JsonInstanceInspector.java
/*
* Copyright (C) 2017 Red Hat, Inc.
*
* 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 io.atlasmap.json.inspect;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ValueNode;
import io.atlasmap.json.core.JsonComplexTypeFactory;
import io.atlasmap.json.v2.AtlasJsonModelFactory;
import io.atlasmap.json.v2.JsonComplexType;
import io.atlasmap.json.v2.JsonDocument;
import io.atlasmap.json.v2.JsonField;
import io.atlasmap.json.v2.JsonFields;
import io.atlasmap.v2.CollectionType;
import io.atlasmap.v2.FieldStatus;
import io.atlasmap.v2.FieldType;
/**
* JSON instance document inspector. It consumes JSON instance document as an example
* and build a AtlasMap Document model object from it.
*/
public class JsonInstanceInspector implements JsonInspector {
private static final Logger LOG = LoggerFactory.getLogger(JsonInstanceInspector.class);
private static JsonInstanceInspector myself = new JsonInstanceInspector();
private JsonInstanceInspector() {
}
public static JsonInstanceInspector instance() {
return myself;
}
public JsonDocument inspect(String instance) throws JsonInspectionException {
if (LOG.isTraceEnabled()) {
LOG.trace("Start JSON instance inspection: {}", instance);
}
if (instance == null || instance.isEmpty()) {
throw new IllegalArgumentException("JSON instance cannot be null");
}
try {
JsonDocument jsonDocument = AtlasJsonModelFactory.createJsonDocument();
ObjectMapper objectMapper = new ObjectMapper()
.enable(MapperFeature.BLOCK_UNSAFE_POLYMORPHIC_BASE_TYPES);
JsonNode rootNode = objectMapper.readTree(instance);
if (rootNode.isObject()) {
Iterator<Entry<String, JsonNode>> fields = rootNode.fields();
while (fields.hasNext()) {
Entry<String, JsonNode> e = fields.next();
String key = e.getKey();
JsonNode node = e.getValue();
if (node.isObject()) {
handleObjectNode(jsonDocument, null, key, (ObjectNode)node, false);
} else if (node.isArray()) {
handleArrayNode(jsonDocument, null, key, (ArrayNode)node);
} else {
createChildJsonField(jsonDocument, null, key, (ValueNode)node, false);
}
}
} else if (rootNode.isArray()) {
handleArrayNode(jsonDocument, null, "", (ArrayNode)rootNode);
} else {
throw new IllegalArgumentException("JSON root must be object or array");
}
return jsonDocument;
} catch (IOException e) {
throw new JsonInspectionException(e);
}
}
private void handleObjectNode(JsonDocument rootDocument, JsonComplexType parent, String key, ObjectNode objectNode, boolean isArray) {
if (LOG.isTraceEnabled()) {
LOG.trace("Handling object node: {}", objectNode);
}
JsonComplexType complexType = createChildJsonComplexType(rootDocument, parent, key, isArray);
Iterator<Entry<String, JsonNode>> subFields = objectNode.fields();
while (subFields.hasNext()) {
Entry<String, JsonNode> e = subFields.next();
String subKey = e.getKey();
JsonNode subNode = e.getValue();
if (subNode.isObject()) {
handleObjectNode(rootDocument, complexType, subKey, (ObjectNode)subNode, false);
} else if (subNode.isArray()) {
handleArrayNode(rootDocument, complexType, subKey, (ArrayNode)subNode);
} else {
createChildJsonField(rootDocument, complexType, subKey, (ValueNode)subNode, false);
}
}
}
private void handleArrayNode(JsonDocument rootDocument, JsonComplexType parent, String key, ArrayNode arrayNode) {
if (LOG.isTraceEnabled()) {
LOG.trace("Handling array node: {}", arrayNode);
}
if (arrayNode.size() == 0) {
LOG.warn("Ignoring empty JSON array: {}", arrayNode);
return;
}
// TODO: Look into other items as well - they might have more fields first item doesn't have
JsonNode sample = arrayNode.get(0);
if (sample.isObject()) {
handleObjectNode(rootDocument, parent, key, (ObjectNode)sample, true);
} else if (sample.isArray()) {
throw new IllegalArgumentException("Nested JSON array is not supported");
} else {
createChildJsonField(rootDocument, parent, key, (ValueNode)sample, true);
}
}
private JsonComplexType createChildJsonComplexType(JsonDocument rootDocument, JsonComplexType parent, String key, boolean isArray) {
if (LOG.isTraceEnabled()) {
LOG.trace("Creating JSON complex type (array:{}): {}", isArray, key);
}
JsonComplexType jsonComplexType = JsonComplexTypeFactory.createJsonComlexField();
jsonComplexType.setJsonFields(new JsonFields());
jsonComplexType.setName(key);
jsonComplexType.setStatus(FieldStatus.SUPPORTED);
String path = (parent != null ? parent.getPath() : "").concat("/").concat(key != null ? key : "");
if (isArray) {
// JSON array is a list in AtlasMap
path = path.concat("<>");
jsonComplexType.setCollectionType(CollectionType.LIST);
}
jsonComplexType.setPath(path);
if (parent != null) {
parent.getJsonFields().getJsonField().add(jsonComplexType);
} else {
rootDocument.getFields().getField().add(jsonComplexType);
}
return jsonComplexType;
}
private JsonField createChildJsonField(JsonDocument rootDocument, JsonComplexType parent, String key, ValueNode valueNode, boolean isArray) {
if (LOG.isTraceEnabled()) {
LOG.trace("Creating JSON field (array:{}): {}", isArray, key);
}
JsonField field = AtlasJsonModelFactory.createJsonField();
field.setName(key);
String path = (parent != null ? parent.getPath() : "").concat("/").concat(key != null ? key : "");
if (isArray) {
// JSON array is a list in AtlasMap
path = path.concat("<>");
field.setCollectionType(CollectionType.LIST);
}
field.setPath(path);
if (parent != null) {
parent.getJsonFields().getJsonField().add(field);
} else {
rootDocument.getFields().getField().add(field);
}
LOG.trace("VALUE IS A " + valueNode.getNodeType().name());
if (valueNode.isNumber()) {
if (valueNode.isInt()) {
field.setFieldType(FieldType.INTEGER);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.intValue());
} else if (valueNode.isBigInteger()) {
field.setFieldType(FieldType.BIG_INTEGER);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.bigIntegerValue());
} else if (valueNode.isFloat()) {
field.setFieldType(FieldType.FLOAT);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.floatValue());
} else if (valueNode.isDouble()) {
field.setFieldType(FieldType.DOUBLE);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.asDouble());
} else if (valueNode.isBigDecimal()) {
field.setFieldType(FieldType.DECIMAL);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.decimalValue());
} else if (valueNode.isShort()) {
field.setFieldType(FieldType.SHORT);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.shortValue());
} else if (valueNode.isLong()) {
field.setFieldType(FieldType.LONG);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.longValue());
}
} else if (valueNode.isTextual()) {
field.setFieldType(FieldType.STRING);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.textValue());
} else if (valueNode.isBoolean()) {
field.setFieldType(FieldType.BOOLEAN);
field.setStatus(FieldStatus.SUPPORTED);
field.setValue(valueNode.booleanValue());
} else if (valueNode.isBinary() || valueNode.isPojo()) {
field.setFieldType(FieldType.UNSUPPORTED);
field.setStatus(FieldStatus.UNSUPPORTED);
}
return field;
}
}