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.utils;
  
  import com.fasterxml.jackson.core.JsonGenerator;
  import com.fasterxml.jackson.core.JsonParser;
  import com.fasterxml.jackson.core.ObjectCodec;
  import com.fasterxml.jackson.core.TreeNode;
  import com.fasterxml.jackson.core.Version;
  import com.fasterxml.jackson.core.type.TypeReference;
  import com.fasterxml.jackson.databind.DeserializationContext;
  import com.fasterxml.jackson.databind.DeserializationFeature;
  import com.fasterxml.jackson.databind.JsonDeserializer;
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.JsonSerializer;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.SerializerProvider;
  import com.fasterxml.jackson.databind.module.SimpleModule;
  import com.fasterxml.jackson.databind.node.ArrayNode;
  import com.fasterxml.jackson.databind.node.ObjectNode;
  import org.apache.atlas.model.discovery.AtlasSearchResult.AtlasFullTextResult;
  import org.apache.atlas.model.notification.EntityNotification;
  import org.apache.atlas.model.notification.EntityNotification.EntityNotificationType;
  import org.apache.atlas.model.notification.EntityNotification.EntityNotificationV2;
  import org.apache.atlas.model.notification.HookNotification;
  import org.apache.atlas.model.notification.HookNotification.EntityCreateRequestV2;
  import org.apache.atlas.model.notification.HookNotification.EntityDeleteRequestV2;
  import org.apache.atlas.model.notification.HookNotification.EntityPartialUpdateRequestV2;
  import org.apache.atlas.model.notification.HookNotification.EntityUpdateRequestV2;
  import org.apache.atlas.model.notification.HookNotification.HookNotificationType;
  import org.apache.atlas.model.notification.ImportNotification;
  import org.apache.atlas.model.typedef.AtlasBaseTypeDef;
  import org.apache.atlas.v1.model.instance.AtlasSystemAttributes;
  import org.apache.atlas.v1.model.instance.Id;
  import org.apache.atlas.v1.model.instance.Referenceable;
  import org.apache.atlas.v1.model.instance.Struct;
  import org.apache.atlas.v1.model.notification.EntityNotificationV1;
  import org.apache.atlas.v1.model.notification.HookNotificationV1.EntityCreateRequest;
  import org.apache.atlas.v1.model.notification.HookNotificationV1.EntityDeleteRequest;
  import org.apache.atlas.v1.model.notification.HookNotificationV1.EntityPartialUpdateRequest;
  import org.apache.atlas.v1.model.notification.HookNotificationV1.EntityUpdateRequest;
  import org.apache.atlas.v1.model.notification.HookNotificationV1.TypeRequest;
  import org.apache.commons.collections.MapUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.text.DateFormat;
  import java.text.ParseException;
  import java.text.SimpleDateFormat;
  import java.util.Collection;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Map;
  
  public class AtlasJson {
      private static final Logger LOG = LoggerFactory.getLogger(AtlasJson.class);
  
      private static final ObjectMapper mapper         = new ObjectMapper().configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
      private static final ObjectMapper mapperV1       = new ObjectMapper().configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
      private static final ObjectMapper mapperV1Search = new ObjectMapper().configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true);
  
      private static final String V1_KEY_$TYPENAME          = "$typeName$";
      private static final String V1_KEY_$ID                = "$id$";
      private static final String V1_KEY_$SYSTEM_ATTRIBUTES = "$systemAttributes$";
      private static final String V1_KEY_$TRAITS            = "$traits$";
      private static final String V1_KEY_TYPENAME           = "typeName";
      private static final String V1_KEY_ID                 = "id";
      private static final String V1_KEY_GUID               = "guid";
      private static final String V1_KEY_SCORE              = "score";
      private static final String V1_KEY_VERSION            = "version";
      private static final String V1_KEY_STATE              = "state";
      private static final String V1_KEY_CREATED_BY         = "createdBy";
      private static final String V1_KEY_MODIFIED_BY        = "modifiedBy";
      private static final String V1_KEY_CREATED_TIME       = "createdTime";
      private static final String V1_KEY_MODIFIED_TIME      = "modifiedTime";
  
      private AtlasJson() {
          // to block instantiation
      }
  
      public static String toJson(Object obj) {
          String ret;
  
          try {
              if (obj instanceof JsonNode && ((JsonNode) obj).isTextual()) {
                  ret = ((JsonNode) obj).textValue();
              } else {
                  ret = mapper.writeValueAsString(obj);
              }
          } catch (IOException e) {
              LOG.error("AtlasJson.toJson()", e);
  
              ret = null;
          }
          return ret;
      }
  
      public static <T> T fromLinkedHashMap(Object linkedHashMap, Class<T> type) {
          T ret = null;
  
          if (linkedHashMap != null) {
              ret = mapper.convertValue(linkedHashMap, type);
  
              if (ret instanceof Struct) {
                  ((Struct) ret).normalize();
              }
          }
  
          return ret;
      }
  
      public static <T> T fromJson(String jsonStr, Class<T> type) {
          T ret = null;
  
          if (jsonStr != null) {
              try {
                  ret = mapper.readValue(jsonStr, type);
  
                  if (ret instanceof Struct) {
                      ((Struct) ret).normalize();
                  }
              } catch (IOException e) {
                  LOG.error("AtlasType.fromJson()", e);
  
                  ret = null;
              }
          }
  
          return ret;
      }
  
      public static <T> T fromJson(String jsonStr, TypeReference<T> type) {
          T ret = null;
  
          if (jsonStr != null) {
              try {
                  ret = mapper.readValue(jsonStr, type);
  
                  if (ret instanceof Struct) {
                      ((Struct) ret).normalize();
                  }
              } catch (IOException e) {
                  LOG.error("AtlasType.fromJson()", e);
  
                  ret = null;
              }
          }
  
          return ret;
      }
  
      public static <T> T fromJson(InputStream inputStream, Class<T> type) throws IOException {
          T ret = null;
  
          if (inputStream != null) {
              ret = mapper.readValue(inputStream, type);
  
              if (ret instanceof Struct) {
                  ((Struct) ret).normalize();
              }
          }
  
          return ret;
      }
  
      public static String toV1Json(Object obj) {
          return toJson(obj);
      }
  
      public static <T> T fromV1Json(String jsonStr, Class<T> type) {
          return fromJson(jsonStr, type);
      }
  
      public static <T> T fromV1Json(String jsonStr, TypeReference<T> type) {
          return fromJson(jsonStr, type);
      }
  
      public static String toV1SearchJson(Object obj) {
          String ret;
          try {
              ret = mapperV1Search.writeValueAsString(obj);
          } catch (IOException e) {
              LOG.error("AtlasType.toV1Json()", e);
  
              ret = null;
          }
          return ret;
      }
  
      public static ObjectNode createV1ObjectNode() {
          return mapperV1.createObjectNode();
      }
  
      public static ObjectNode createV1ObjectNode(String key, Object value) {
          ObjectNode ret = mapperV1.createObjectNode();
  
          ret.putPOJO(key, value);
  
          return ret;
      }
  
      public static ArrayNode createV1ArrayNode() {
          return mapperV1.createArrayNode();
      }
  
      public static ArrayNode createV1ArrayNode(Collection<?> array) {
          ArrayNode ret = mapper.createArrayNode();
  
          for (Object elem : array) {
              ret.addPOJO(elem);
          }
  
          return ret;
      }
  
      public static JsonNode parseToV1JsonNode(String json) throws IOException {
          return mapper.readTree(json);
      }
  
      public static ArrayNode parseToV1ArrayNode(String json) throws IOException {
          JsonNode jsonNode = mapper.readTree(json);
  
          if (jsonNode instanceof ArrayNode) {
              return (ArrayNode) jsonNode;
          }
  
          throw new IOException("not an array");
      }
  
      public static ArrayNode parseToV1ArrayNode(Collection<String> jsonStrings) throws IOException {
          ArrayNode ret = createV1ArrayNode();
  
          for (String json : jsonStrings) {
              JsonNode jsonNode = mapper.readTree(json);
  
              ret.add(jsonNode);
          }
  
          return ret;
      }
  
      public static ObjectCodec getMapper() {
          return mapper;
      }
  
      static class DateSerializer extends JsonSerializer<Date> {
          @Override
          public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (value != null) {
                  jgen.writeString(AtlasBaseTypeDef.getDateFormatter().format(value));
              }
          }
      }
  
      static class DateDeserializer extends JsonDeserializer<Date> {
          @Override
          public Date deserialize(JsonParser parser, DeserializationContext context) throws IOException {
              Date   ret   = null;
              String value = parser.readValueAs(String.class);
  
              if (value != null) {
                  try {
                      ret = AtlasBaseTypeDef.getDateFormatter().parse(value);
                  } catch (ParseException excp) {
                      // ignored
                  }
              }
  
              return ret;
          }
      }
  
      static class ReferenceableSerializer extends JsonSerializer<Referenceable> {
          @Override
          public void serialize(Referenceable value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (value != null) {
                  mapperV1.writeValue(jgen, value);
              }
          }
      }
  
      static class ReferenceableDeserializer extends JsonDeserializer<Referenceable> {
          @Override
          public Referenceable deserialize(JsonParser parser, DeserializationContext context) throws IOException {
              return mapperV1.readValue(parser, Referenceable.class);
          }
      }
  
      static class StructSerializer extends JsonSerializer<Struct> {
          @Override
          public void serialize(Struct value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (value != null) {
                  mapperV1.writeValue(jgen, value);
              }
          }
      }
  
      static class StructDeserializer extends JsonDeserializer<Struct> {
          @Override
          public Struct deserialize(JsonParser parser, DeserializationContext context) throws IOException {
              return mapperV1.readValue(parser, Struct.class);
          }
      }
  
      static class IdSerializer extends JsonSerializer<Id> {
          @Override
          public void serialize(Id value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (value != null) {
                  mapperV1.writeValue(jgen, value);
              }
          }
      }
  
      static class IdDeserializer extends JsonDeserializer<Id> {
          @Override
          public Id deserialize(JsonParser parser, DeserializationContext context) throws IOException {
              return mapperV1.readValue(parser, Id.class);
          }
      }
  
      static class HookNotificationDeserializer extends JsonDeserializer<HookNotification> {
          @Override
          public HookNotification deserialize(JsonParser parser, DeserializationContext context) throws IOException {
              HookNotification     ret              = null;
              ObjectCodec          mapper           = parser.getCodec();
              TreeNode             root             = mapper.readTree(parser);
              JsonNode             typeNode         = root != null ? (JsonNode) root.get("type") : null;
              String               strType          = typeNode != null ? typeNode.asText() : null;
              HookNotificationType notificationType = strType != null ? HookNotificationType.valueOf(strType) : null;
  
              if (notificationType != null) {
                  switch (notificationType) {
                      case TYPE_CREATE:
                      case TYPE_UPDATE:
                          ret = mapper.treeToValue(root, TypeRequest.class);
                          break;
  
                      case ENTITY_CREATE:
                          ret = mapper.treeToValue(root, EntityCreateRequest.class);
                          break;
  
                      case ENTITY_PARTIAL_UPDATE:
                          ret = mapper.treeToValue(root, EntityPartialUpdateRequest.class);
                          break;
  
                      case ENTITY_FULL_UPDATE:
                          ret = mapper.treeToValue(root, EntityUpdateRequest.class);
                          break;
  
                      case ENTITY_DELETE:
                          ret = mapper.treeToValue(root, EntityDeleteRequest.class);
                          break;
  
                      case ENTITY_CREATE_V2:
                          ret = mapper.treeToValue(root, EntityCreateRequestV2.class);
                          break;
  
                      case ENTITY_PARTIAL_UPDATE_V2:
                          ret = mapper.treeToValue(root, EntityPartialUpdateRequestV2.class);
                          break;
  
                      case ENTITY_FULL_UPDATE_V2:
                          ret = mapper.treeToValue(root, EntityUpdateRequestV2.class);
                          break;
  
                      case ENTITY_DELETE_V2:
                          ret = mapper.treeToValue(root, EntityDeleteRequestV2.class);
                          break;
  
                      case IMPORT_TYPES_DEF:
                          ret = mapper.treeToValue(root, ImportNotification.AtlasTypesDefImportNotification.class);
                          break;
  
                      case IMPORT_ENTITY:
                          ret = mapper.treeToValue(root, ImportNotification.AtlasEntityImportNotification.class);
                          break;
                  }
              }
  
              return ret;
          }
      }
  
      static class EntityNotificationDeserializer extends JsonDeserializer<EntityNotification> {
          @Override
          public EntityNotification deserialize(JsonParser parser, DeserializationContext context) throws IOException {
              EntityNotification     ret              = null;
              ObjectCodec            mapper           = parser.getCodec();
              TreeNode               root             = mapper.readTree(parser);
              JsonNode               typeNode         = root != null ? (JsonNode) root.get("type") : null;
              String                 strType          = typeNode != null ? typeNode.asText() : null;
              EntityNotificationType notificationType = strType != null ? EntityNotificationType.valueOf(strType) : EntityNotificationType.ENTITY_NOTIFICATION_V1;
  
              if (root != null) {
                  switch (notificationType) {
                      case ENTITY_NOTIFICATION_V1:
                          ret = mapper.treeToValue(root, EntityNotificationV1.class);
                          break;
  
                      case ENTITY_NOTIFICATION_V2:
                          ret = mapper.treeToValue(root, EntityNotificationV2.class);
                          break;
                  }
              }
  
              return ret;
          }
      }
  
      static class V1SearchReferenceableSerializer extends JsonSerializer<Referenceable> {
          @Override
          public void serialize(Referenceable entity, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (entity != null) {
                  Map<String, Object> valueMap = entity.getValues() != null ? new HashMap<>(entity.getValues()) : new HashMap<>();
  
                  if (entity.getTypeName() != null) {
                      valueMap.put(V1_KEY_$TYPENAME, entity.getTypeName());
                  }
  
                  if (entity.getId() != null) {
                      valueMap.put(V1_KEY_$ID, entity.getId());
                  }
  
                  if (entity.getSystemAttributes() != null) {
                      valueMap.put(V1_KEY_$SYSTEM_ATTRIBUTES, entity.getSystemAttributes());
                  }
  
                  if (MapUtils.isNotEmpty(entity.getTraits())) {
                      valueMap.put(V1_KEY_$TRAITS, entity.getTraits());
                  }
  
                  jgen.writeObject(valueMap);
              }
          }
      }
  
      static class V1SearchStructSerializer extends JsonSerializer<Struct> {
          @Override
          public void serialize(Struct struct, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (struct != null) {
                  Map<String, Object> valueMap = struct.getValues() != null ? new HashMap<>(struct.getValues()) : new HashMap<>();
  
                  valueMap.put(V1_KEY_$TYPENAME, struct.getTypeName());
  
                  jgen.writeObject(valueMap);
              }
          }
      }
  
      static class V1SearchIdSerializer extends JsonSerializer<Id> {
          @Override
          public void serialize(Id id, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (id != null) {
                  Map<String, Object> valueMap = new HashMap<>();
  
                  valueMap.put(V1_KEY_ID, id._getId());
                  valueMap.put(V1_KEY_$TYPENAME, id.getTypeName());
                  valueMap.put(V1_KEY_VERSION, id.getVersion());
  
                  if (id.getState() != null) {
                      valueMap.put(V1_KEY_STATE, id.getState().toString());
                  }
  
                  jgen.writeObject(valueMap);
              }
          }
      }
  
      static class V1SearchSystemAttributesSerializer extends JsonSerializer<AtlasSystemAttributes> {
          private static final ThreadLocal<DateFormat> V1_SEARCH_RESULT_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy"));
  
          @Override
          public void serialize(AtlasSystemAttributes systemAttributes, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (systemAttributes != null) {
                  Map<String, Object> valueMap = new HashMap<>();
  
                  valueMap.put(V1_KEY_CREATED_BY, systemAttributes.getCreatedBy());
                  valueMap.put(V1_KEY_MODIFIED_BY, systemAttributes.getModifiedBy());
  
                  if (systemAttributes.getCreatedTime() != null) {
                      valueMap.put(V1_KEY_CREATED_TIME, V1_SEARCH_RESULT_DATE_FORMAT.get().format(systemAttributes.getCreatedTime()));
                  }
  
                  if (systemAttributes.getModifiedTime() != null) {
                      valueMap.put(V1_KEY_MODIFIED_TIME, V1_SEARCH_RESULT_DATE_FORMAT.get().format(systemAttributes.getModifiedTime()));
                  }
  
                  jgen.writeObject(valueMap);
              }
          }
      }
  
      static class V1SearchFullTextResultSerializer extends JsonSerializer<AtlasFullTextResult> {
          @Override
          public void serialize(AtlasFullTextResult result, JsonGenerator jgen, SerializerProvider provider) throws IOException {
              if (result != null && result.getEntity() != null) {
                  Map<String, Object> valueMap = new HashMap<>();
  
                  valueMap.put(V1_KEY_GUID, result.getEntity().getGuid());
                  valueMap.put(V1_KEY_TYPENAME, result.getEntity().getTypeName());
                  valueMap.put(V1_KEY_SCORE, result.getScore());
  
                  jgen.writeObject(valueMap);
              }
          }
      }
  
      static {
          SimpleModule atlasSerDeModule = new SimpleModule("AtlasSerDe", new Version(1, 0, 0, null));
  
          atlasSerDeModule.addSerializer(Referenceable.class, new ReferenceableSerializer());
          atlasSerDeModule.addDeserializer(Referenceable.class, new ReferenceableDeserializer());
          atlasSerDeModule.addSerializer(Struct.class, new StructSerializer());
          atlasSerDeModule.addDeserializer(Struct.class, new StructDeserializer());
          atlasSerDeModule.addSerializer(Id.class, new IdSerializer());
          atlasSerDeModule.addDeserializer(Id.class, new IdDeserializer());
          atlasSerDeModule.addDeserializer(HookNotification.class, new HookNotificationDeserializer());
          atlasSerDeModule.addDeserializer(EntityNotification.class, new EntityNotificationDeserializer());
  
          mapper.registerModule(atlasSerDeModule);
  
          SimpleModule atlasSerDeV1Module = new SimpleModule("AtlasSerDeV1", new Version(1, 0, 0, null));
  
          atlasSerDeV1Module.addSerializer(Date.class, new DateSerializer());
          atlasSerDeV1Module.addDeserializer(Date.class, new DateDeserializer());
  
          mapperV1.registerModule(atlasSerDeV1Module);
  
          SimpleModule searchResultV1SerDeModule = new SimpleModule("SearchResultV1SerDe", new Version(1, 0, 0, null));
  
          searchResultV1SerDeModule.addSerializer(Referenceable.class, new V1SearchReferenceableSerializer());
          searchResultV1SerDeModule.addSerializer(Struct.class, new V1SearchStructSerializer());
          searchResultV1SerDeModule.addSerializer(Id.class, new V1SearchIdSerializer());
          searchResultV1SerDeModule.addSerializer(AtlasSystemAttributes.class, new V1SearchSystemAttributesSerializer());
          searchResultV1SerDeModule.addSerializer(AtlasFullTextResult.class, new V1SearchFullTextResultSerializer());
          searchResultV1SerDeModule.addSerializer(Date.class, new DateSerializer());
  
          mapperV1Search.registerModule(searchResultV1SerDeModule);
      }
  }
  