/**
*
*/
package com.ebay.cloud.cms.typsafe.metadata.model;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.JsonNodeFactory;
import org.codehaus.jackson.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ebay.cloud.cms.typsafe.exception.CMSEntityException;
import com.ebay.cloud.cms.typsafe.metadata.model.MetaField.CardinalityEnum;
import com.ebay.cloud.cms.typsafe.metadata.model.MetaField.DataTypeEnum;
import com.ebay.cloud.cms.typsafe.metadata.model.MetaRelationship.ConsistencyTypeEnum;
import com.ebay.cloud.cms.typsafe.metadata.model.MetaRelationship.RelationTypeEnum;
/**
* @author liasu
*
*/
@SuppressWarnings("deprecation")
public class MetadataManager {
private Map<String, MetaClass> metadatas = new HashMap<String, MetaClass>();
private static final ObjectMapper OM = new ObjectMapper();
private static final List<String> STATUS_VALUES = Collections.unmodifiableList(Arrays.asList("active", "deleted"));
private static final Logger logger = LoggerFactory.getLogger(MetadataManager.class);
public Collection<String> getMetadataNames() {
return metadatas.keySet();
}
public MetaClass getMetadata(String metaName) {
return metadatas.get(metaName);
}
public Map<String, MetaClass> getMetadatas() {
return Collections.unmodifiableMap(metadatas);
}
/**
* For some projects, jackson must be 1.7, which means some configuration
* like null handling in jackson won't have a consistent way to set in
* jackson. Choose to manually load the json.
*/
public static MetadataManager load(JsonNode node) {
MetadataManager mm = new MetadataManager();
List<MetaRelationship> embedReference = new ArrayList<MetaRelationship>();
if (node.isArray()) {
ArrayNode array = (ArrayNode) node;
for (JsonNode metaNode : array) {
MetaClass meta = loadMetadata(metaNode, embedReference);
mm.metadatas.put(meta.getName(), meta);
}
for (MetaRelationship mr : embedReference) {
String relName = mr.getRefDataType();
mr.setRefDataType(mm.resolveEmbedReferenceName(relName));
}
}
return mm;
}
public static MetadataManager load(String allMetaInOne) {
try {
JsonNode node = OM.readTree(allMetaInOne);
if (!node.isArray() && node.isObject()) {
// wrapper the object node
ArrayNode an = JsonNodeFactory.instance.arrayNode();
an.add(node);
node = an;
}
return load(node);
} catch (Exception e) {
throw new CMSEntityException(String.format("fail to parse meta json: %s", allMetaInOne), e);
}
}
//
// Find the last embed name inside embed reference
//
public String resolveEmbedReferenceName(String relName) {
String[] relTypes = relName.split("\\.");
if (relTypes.length == 1) {
return relTypes[0];
}
MetaClass rootMc = getMetadata(relTypes[0]);
MetaClass lastMc = rootMc;
for (int i = 1; i < relTypes.length; i++) {
MetaRelationship mr = (MetaRelationship) lastMc.getField(relTypes[i]);
String name = resolveEmbedReferenceName(mr.getRefDataType());
lastMc = getMetadata(name);
}
return lastMc.getName();
}
private static MetaClass loadMetadata(JsonNode metaNode, List<MetaRelationship> embedReference) {
MetaClass meta = new MetaClass();
meta.setName(getStringField(metaNode, "name"));
meta.setPluralName(getStringField(metaNode, "pluralName"));
meta.setParent(getStringField(metaNode, "parent"));
meta.setRepository(getStringField(metaNode, "repository"));
meta.set_id(getStringField(metaNode, "id"));
meta.setEmbed(getBooleanField(metaNode, "embed"));
meta.setInner(getBooleanField(metaNode, "inner"));
meta.setDescription(getStringField(metaNode, "description"));
meta.setAllowFullTableScan(getBooleanField(metaNode, "allowFullTableScan"));
JsonNode fieldNodes = metaNode.get("fields");
Iterator<String> it = fieldNodes.getFieldNames();
while (it.hasNext()) {
String fName = it.next();
MetaField field = loadMetafield(fName, fieldNodes.get(fName), embedReference);
meta.addField(field);
}
// internal attributes
meta.addField(createInternalAttribute("_oid", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_type", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_branch", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_version", DataTypeEnum.INTEGER));
meta.addField(createInternalAttribute("_createTime", DataTypeEnum.DATE));
meta.addField(createInternalAttribute("_lastmodified", DataTypeEnum.DATE));
MetaAttribute statusAttr = createInternalAttribute("_status", DataTypeEnum.ENUM);
statusAttr.setEnumValues(STATUS_VALUES);
meta.addField(statusAttr);
meta.addField(createInternalAttribute("_pversion", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_comment", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_user", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_modifier", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_creator", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_hostentity", DataTypeEnum.STRING));
meta.addField(createInternalAttribute("_shardkey", DataTypeEnum.STRING));
// indexes
JsonNode optionNode = metaNode.get("options");
addOptions(optionNode, meta);
return meta;
}
private static MetaAttribute createInternalAttribute(String name, DataTypeEnum dataType) {
MetaAttribute attr = new MetaAttribute();
attr.setInternal(true);
attr.setName(name);
attr.setCardinality(CardinalityEnum.One);
attr.setConstant(false);
attr.setDataType(dataType);
return attr;
}
private static MetaField loadMetafield(String fName, JsonNode node, List<MetaRelationship> embedReference) {
String dt = getStringField(node, "dataType");
DataTypeEnum dataType = DataTypeEnum.fromString(dt);
MetaField field = null;
if (dataType == DataTypeEnum.RELATIONSHIP) {
field = new MetaRelationship();
loadRelationship((MetaRelationship) field, node, embedReference);
} else {
field = new MetaAttribute();
loadAttribute((MetaAttribute) field, node);
}
field.setName(fName);
field.setConstant(getBooleanField(node, "constant"));
field.setDescription(getStringField(node, "description"));
field.setMandatory(getBooleanField(node, "mandatory"));
String car = getStringField(node, "cardinality");
field.setCardinality(CardinalityEnum.fromString(car));
field.setDataType(dataType);
return field;
}
private static void loadAttribute(MetaAttribute field, JsonNode node) {
field.setDefaultValue(getStringField(node, "defaultValue"));
field.setExpression(getStringField(node, "expression"));
field.setValidation(getStringField(node, "validation"));
ArrayNode enumArray = (ArrayNode) node.get("enumValues");
if (enumArray != null) {
for (JsonNode valNode : enumArray) {
field.addEnumValue(valNode.getValueAsText());
}
}
}
private static void loadRelationship(MetaRelationship field, JsonNode node, List<MetaRelationship> embedReference) {
String refDataType = getStringField(node, "refDataType");
field.setRefDataType(refDataType);
if (isEmbedReference(refDataType)) {
embedReference.add(field);
}
field.setSrcDataType(getStringField(node, "srcDataType"));
field.setRefRepository(getStringField(node, "refRepository"));
field.setCascade(getBooleanField(node, "cascade"));
String relType = getStringField(node, "relationType");
if (relType != null) {
field.setRelationType(RelationTypeEnum.fromString(relType));
}
String consistType = getStringField(node, "consistencyType");
if (consistType != null) {
field.setConsistencyType(ConsistencyTypeEnum.fromString(consistType));
}
}
private static boolean isEmbedReference(String refDataType) {
return refDataType.contains(".");
}
private static void addOptions(JsonNode optionNode, MetaClass meta) {
if (optionNode == null) {
return;
}
ObjectNode indexNode = (ObjectNode) optionNode.get("indexes");
if (indexNode == null) {
return;
}
Map<String, IndexInfo> indexes = meta.getOptions().getIndexes();
Iterator<Entry<String, JsonNode>> it = indexNode.getFields();
while (it.hasNext()) {
Entry<String, JsonNode> indexEntry = it.next();
ObjectNode iNode = (ObjectNode) indexEntry.getValue();
try {
IndexInfo info = OM.readValue(iNode, IndexInfo.class);
indexes.put(indexEntry.getKey(), info);
} catch (Exception e) {
logger.error(MessageFormat.format("load index failed! This is ignored. The payload of index is {0}", iNode), e);
}
}
}
private static String getStringField(JsonNode node, String field) {
if (node.has(field) && !node.get(field).isNull()) {
return node.get(field).getValueAsText();
} else {
return null;
}
}
private static Boolean getBooleanField(JsonNode node, String field) {
if (node.has(field) && !node.get(field).isNull()) {
return node.get(field).getValueAsBoolean();
} else {
return false;
}
}
}