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.graphdb.janus.migration;
  
  import org.apache.atlas.repository.Constants;
  import org.apache.atlas.type.AtlasBuiltInTypes.AtlasBigDecimalType;
  import org.apache.atlas.type.AtlasBuiltInTypes.AtlasBigIntegerType;
  import org.apache.tinkerpop.gremlin.structure.Edge;
  import org.apache.tinkerpop.gremlin.structure.Graph;
  import org.apache.tinkerpop.gremlin.structure.Graph.Features.EdgeFeatures;
  import org.apache.tinkerpop.gremlin.structure.Graph.Features.VertexFeatures;
  import org.apache.tinkerpop.gremlin.structure.T;
  import org.apache.tinkerpop.gremlin.structure.Vertex;
  import org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality;
  import org.apache.tinkerpop.shaded.jackson.databind.JsonNode;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  public class GraphSONUtility {
      private static final Logger LOG = LoggerFactory.getLogger(GraphSONUtility.class);
  
      private static final String              EMPTY_STRING   = "";
      private static final AtlasBigIntegerType bigIntegerType = new AtlasBigIntegerType();
      private static final AtlasBigDecimalType bigDecimalType = new AtlasBigDecimalType();
  
      private final ElementProcessors elementProcessors;
  
      public GraphSONUtility(final ElementProcessors elementProcessors) {
          this.elementProcessors = elementProcessors;
      }
  
      static Map<String, Object> readProperties(final JsonNode node) {
          final Map<String, Object>                   map      = new HashMap<>();
          final Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
  
          while (iterator.hasNext()) {
              final Map.Entry<String, JsonNode> entry = iterator.next();
  
              if (!isReservedKey(entry.getKey())) {
                  // it generally shouldn't be as such but graphson containing null values can't be shoved into
                  // element property keys or it will result in error
                  final Object o = readProperty(entry.getValue());
  
                  if (o != null) {
                      map.put(entry.getKey(), o);
                  }
              }
          }
  
          return map;
      }
  
      static Object getTypedValueFromJsonNode(final JsonNode node) {
          Object theValue = null;
  
          if (node != null && !node.isNull()) {
              if (node.isBoolean()) {
                  theValue = node.booleanValue();
              } else if (node.isDouble()) {
                  theValue = node.doubleValue();
              } else if (node.isFloatingPointNumber()) {
                  theValue = node.floatValue();
              } else if (node.isInt()) {
                  theValue = node.intValue();
              } else if (node.isLong()) {
                  theValue = node.longValue();
              } else if (node.isTextual()) {
                  theValue = node.textValue();
              } else if (node.isBigDecimal()) {
                  theValue = node.decimalValue();
              } else if (node.isBigInteger()) {
                  theValue = node.bigIntegerValue();
              } else if (node.isArray()) {
                  // this is an array so just send it back so that it can be
                  // reprocessed to its primitive components
                  theValue = node;
              } else if (node.isObject()) {
                  // this is an object so just send it back so that it can be
                  // reprocessed to its primitive components
                  theValue = node;
              } else {
                  theValue = node.textValue();
              }
          }
  
          return theValue;
      }
  
      public Map<String, Object> vertexFromJson(Graph g, final JsonNode json) {
          final Map<String, Object> props = readProperties(json);
  
          if (props.containsKey(Constants.TYPENAME_PROPERTY_KEY)) {
              return null;
          }
  
          Map<String, Object> schemaUpdate   = null;
          VertexFeatures      vertexFeatures = g.features().vertex();
          Object              vertexId       = getTypedValueFromJsonNode(json.get(GraphSONTokensTP2._ID));
          Vertex              vertex         = vertexFeatures.willAllowId(vertexId) ? g.addVertex(T.id, vertexId) : g.addVertex();
  
          props.put(Constants.VERTEX_ID_IN_IMPORT_KEY, vertexId);
          elementProcessors.processCollections(Constants.ENTITY_TYPE_PROPERTY_KEY, props);
  
          for (Map.Entry<String, Object> entry : props.entrySet()) {
              try {
                  final Cardinality cardinality = vertexFeatures.getCardinality(entry.getKey());
                  final String      key         = entry.getKey();
                  final Object      val         = entry.getValue();
  
                  if ((cardinality == Cardinality.list || cardinality == Cardinality.set) && (val instanceof Collection)) {
                      for (Object elem : (Collection<?>) val) {
                          vertex.property(key, elem);
                      }
                  } else {
                      vertex.property(key, val);
                  }
              } catch (IllegalArgumentException ex) {
                  schemaUpdate = getSchemaUpdateMap(schemaUpdate);
  
                  if (!schemaUpdate.containsKey("id")) {
                      schemaUpdate.put("id", vertex.id());
                  }
  
                  schemaUpdate.put(entry.getKey(), entry.getValue());
              }
          }
  
          return schemaUpdate;
      }
  
      public Map<String, Object> edgeFromJson(Graph g, MappedElementCache cache, final JsonNode json) {
          final JsonNode nodeLabel = json.get(GraphSONTokensTP2._LABEL);
          String         label     = nodeLabel == null ? EMPTY_STRING : nodeLabel.textValue();
  
          if (label.startsWith("__type.")) {
              return null;
          }
  
          Map<String, Object> schemaUpdate = null;
          Object              edgeId       = null;
  
          try {
              final Vertex in  = getMappedVertex(g, cache, json, GraphSONTokensTP2._IN_V);
              final Vertex out = getMappedVertex(g, cache, json, GraphSONTokensTP2._OUT_V);
  
              if (in == null || out == null) {
                  return null;
              }
  
              edgeId = getTypedValueFromJsonNode(json.get(GraphSONTokensTP2._ID));
  
              final Map<String, Object> props = GraphSONUtility.readProperties(json);
  
              props.put(Constants.EDGE_ID_IN_IMPORT_KEY, edgeId.toString());
  
              label = elementProcessors.updateEdge(in, out, edgeId, label, props);
  
              EdgeFeatures edgeFeatures = g.features().edge();
              final Edge   edge         = edgeFeatures.willAllowId(edgeId) ? out.addEdge(label, in, T.id, edgeId) : out.addEdge(label, in);
  
              for (Map.Entry<String, Object> entry : props.entrySet()) {
                  try {
                      edge.property(entry.getKey(), entry.getValue());
                  } catch (IllegalArgumentException ex) {
                      schemaUpdate = getSchemaUpdateMap(schemaUpdate);
  
                      if (!schemaUpdate.containsKey("id")) {
                          schemaUpdate.put("id", edge.id());
                      }
  
                      schemaUpdate.put(entry.getKey(), entry.getValue());
                  }
              }
          } catch (IllegalArgumentException ex) {
              schemaUpdate = getSchemaUpdateMap(schemaUpdate);
              schemaUpdate.put("oid", edgeId);
          }
  
          return schemaUpdate;
      }
  
      private Map<String, Object> getSchemaUpdateMap(Map<String, Object> schemaUpdate) {
          if (schemaUpdate == null) {
              schemaUpdate = new HashMap<>();
          }
  
          return schemaUpdate;
      }
  
      private Vertex getMappedVertex(Graph gr, MappedElementCache cache, JsonNode json, String direction) {
          Object inVId = GraphSONUtility.getTypedValueFromJsonNode(json.get(direction));
  
          return cache.getMappedVertex(gr, inVId);
      }
  
      private static boolean isReservedKey(final String key) {
          return key.equals(GraphSONTokensTP2._ID) || key.equals(GraphSONTokensTP2._TYPE) || key.equals(GraphSONTokensTP2._LABEL)
                  || key.equals(GraphSONTokensTP2._OUT_V) || key.equals(GraphSONTokensTP2._IN_V);
      }
  
      private static Object readProperty(final JsonNode node) {
          final Object propertyValue;
  
          if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_UNKNOWN)) {
              propertyValue = null;
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_BOOLEAN)) {
              propertyValue = node.get(GraphSONTokensTP2.VALUE).booleanValue();
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_FLOAT)) {
              propertyValue = Float.parseFloat(node.get(GraphSONTokensTP2.VALUE).asText());
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_BYTE)) {
              propertyValue = Byte.parseByte(node.get(GraphSONTokensTP2.VALUE).asText());
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_SHORT)) {
              propertyValue = Short.parseShort(node.get(GraphSONTokensTP2.VALUE).asText());
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_DOUBLE)) {
              propertyValue = node.get(GraphSONTokensTP2.VALUE).doubleValue();
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_INTEGER)) {
              propertyValue = node.get(GraphSONTokensTP2.VALUE).intValue();
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_LONG)) {
              propertyValue = node.get(GraphSONTokensTP2.VALUE).asLong();
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_BIG_DECIMAL)) {
              propertyValue = bigDecimalType.getNormalizedValue(node.get(GraphSONTokensTP2.VALUE));
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_BIG_INTEGER)) {
              propertyValue = bigIntegerType.getNormalizedValue(node.get(GraphSONTokensTP2.VALUE));
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_DATE)) {
              propertyValue = new Date(node.get(GraphSONTokensTP2.VALUE).asLong());
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_STRING)) {
              propertyValue = node.get(GraphSONTokensTP2.VALUE).textValue();
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_LIST)) {
              propertyValue = readProperties(node.get(GraphSONTokensTP2.VALUE).elements());
          } else if (node.get(GraphSONTokensTP2.TYPE).textValue().equals(GraphSONTokensTP2.TYPE_MAP)) {
              propertyValue = readProperties(node.get(GraphSONTokensTP2.VALUE));
          } else {
              propertyValue = node.textValue();
          }
  
          return propertyValue;
      }
  
      private static List<Object> readProperties(final Iterator<JsonNode> listOfNodes) {
          final List<Object> array = new ArrayList<>();
  
          while (listOfNodes.hasNext()) {
              array.add(readProperty(listOfNodes.next()));
          }
  
          return array;
      }
  }
  