/** * */ package com.ebay.cloud.cms.typsafe.entity.internal; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.node.ArrayNode; import org.codehaus.jackson.node.JsonNodeFactory; import org.codehaus.jackson.node.NullNode; import org.codehaus.jackson.node.ObjectNode; import org.codehaus.jackson.node.POJONode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ebay.cloud.cms.typsafe.entity.AbstractCMSEntity; import com.ebay.cloud.cms.typsafe.entity.ICMSEntity; import com.ebay.cloud.cms.typsafe.entity.ICMSEntityVisitor; public class JsonCMSEntity extends AbstractCMSEntity { private static final Logger logger = LoggerFactory.getLogger(JsonCMSEntity.class); private ObjectNode objectNode; private Class<? extends ICMSEntity> metaClass; JsonCMSEntity() { objectNode = JsonNodeFactory.instance.objectNode(); } public JsonCMSEntity(Class<? extends ICMSEntity> metaClass) { this(); this.metaClass = metaClass; } public JsonCMSEntity(ObjectNode given, Class<? extends ICMSEntity> meta) { objectNode = given; this.metaClass = meta; } public boolean hasField(String fieldName) { return objectNode.has(fieldName); } public Object getFieldValue(String fieldName) { if (isJsonType(fieldName)) { return objectNode.get(fieldName); } return convertFromJson(fieldName, objectNode.get(fieldName)); } private boolean isJsonType(String fieldName) { Class<?> clz = ClassUtil.getGetterReturnType(metaClass, fieldName); return clz == JsonNode.class; } private Object convertFromJson(String fieldName, JsonNode jsonNode) { if (jsonNode == null || jsonNode.isNull()) { return null; } if (jsonNode.isArray()) { ArrayNode an = (ArrayNode) jsonNode; List<Object> result = new ArrayList<Object>(); Iterator<JsonNode> nodeItr = an.getElements(); while (nodeItr.hasNext()) { Object val = convertFromJsonSingle(fieldName, nodeItr.next()); if (val instanceof JsonNode) { // WORKAROUND: If return a JsonNode, means this should not be casted as List. For example, it could be a newly added // meta field in server side. In this case, we only return the json node return jsonNode; } result.add(val); } return result; } else { return convertFromJsonSingle(fieldName, jsonNode); } } /** * FIXME: For PaaS integration, we choose jackson 1.7 compatible deprecation API only * @param fieldName */ @SuppressWarnings("deprecation") private Object convertFromJsonSingle(String fieldName, JsonNode next) { if (next.isDouble()) { return next.getValueAsDouble(); } else if (next.isInt()) { return next.getValueAsInt(); } else if (next.isTextual()) { return next.getValueAsText(); } else if (next.isBoolean()) { return next.getValueAsBoolean(); } else if (next.isLong()) { return next.getValueAsLong(); } else if (next.isNull()) { return null; } else if (next.isPojo()) { return ((POJONode)next).getPojo(); } else if (next.isObject()) { ObjectNode objectNode = (ObjectNode) next; if (isReferenceNode(objectNode)) { return new JsonCMSEntity((ObjectNode) next, ClassUtil.getFieldClass(metaClass, fieldName)); } else { return next; } } logger.error(MessageFormat.format( "convert from json encounting un-recoginizable node. The node value representation as : {0}", next.getValueAsText())); return null; } public Collection<String> getFieldNames() { Iterator<String> itr = objectNode.getFieldNames(); List<String> fieldNames = new ArrayList<String>(); while (itr.hasNext()) { fieldNames.add(itr.next()); } return fieldNames; } public void addFieldValue(String fieldName, Object fieldValue) { JsonNode fieldNode = convertToJsonNode(fieldValue); JsonNode node = objectNode.get(fieldName); if (node != null && !node.isArray()) { throw new RuntimeException( MessageFormat .format("call addFieldValue encounting exisitng non-list field values, incorrectly set the field value as non-list value?. Field name {0}", fieldName)); } if (node == null) { node = JsonNodeFactory.instance.arrayNode(); objectNode.put(fieldName, node); } ArrayNode array = (ArrayNode) node; array.add(fieldNode); } public void setFieldValue(String fieldName, Object fieldValue) { JsonNode fieldNode = convertToJsonNode(fieldValue); objectNode.put(fieldName, fieldNode); } @SuppressWarnings({ "rawtypes" }) private JsonNode convertToJsonNode(Object fieldValue) { if (fieldValue == null) { return NullNode.getInstance(); } else if (fieldValue instanceof JsonNode) { return (JsonNode) fieldValue; } Class valueClass = fieldValue.getClass(); if (List.class.isAssignableFrom(valueClass)) { List lists = (List) fieldValue; ArrayNode an = JsonNodeFactory.instance.arrayNode(); for (Object obj : lists) { JsonNode valNode = converToJsonNodeSingle(obj); an.add(valNode); } return an; } else { return converToJsonNodeSingle(fieldValue); } } @SuppressWarnings("rawtypes") private JsonNode converToJsonNodeSingle(Object fieldValue) { if (fieldValue == null) { return JsonNodeFactory.instance.nullNode(); } Class valueClass = fieldValue.getClass(); if (valueClass == Boolean.class) { return JsonNodeFactory.instance.booleanNode((Boolean) fieldValue); } else if (valueClass == Integer.class) { return JsonNodeFactory.instance.numberNode(((Integer) fieldValue).intValue()); } else if (valueClass == Long.class) { return JsonNodeFactory.instance.numberNode(((Long) fieldValue).longValue()); } else if (valueClass == Double.class){ return JsonNodeFactory.instance.numberNode(((Double) fieldValue).doubleValue()); } else if (valueClass.isEnum()) { return JsonNodeFactory.instance.textNode(fieldValue.toString()); } else if (valueClass == String.class) { return JsonNodeFactory.instance.textNode(fieldValue.toString()); } else if (valueClass == Date.class) { return JsonNodeFactory.instance.numberNode(((Date) fieldValue).getTime()); } else if (valueClass == JsonCMSEntity.class) { return ((JsonCMSEntity) fieldValue).getNode(); } else if (valueClass == Map.class) { return JsonNodeFactory.instance.POJONode(fieldValue); } logger.error(MessageFormat.format( "convert object to json node, fieldValue class {0}, with toString() value {1}", fieldValue.getClass() .getName(), fieldValue.toString())); return null; } public void traverse(ICMSEntityVisitor visitor) { Iterator<Map.Entry<String, JsonNode>> subNodeItr = objectNode.getFields(); List<Map.Entry<String, JsonNode>> attributeNode = new ArrayList<Map.Entry<String, JsonNode>>(); List<Map.Entry<String, JsonNode>> referenceNode = new ArrayList<Map.Entry<String, JsonNode>>(); while (subNodeItr.hasNext()) { Entry<String, JsonNode> entry = subNodeItr.next(); JsonNode subNode = entry.getValue(); if (isReferenceNode(subNode)) { referenceNode.add(entry); } else { attributeNode.add(entry); } } // traverse attribute for (Map.Entry<String, JsonNode> entry : attributeNode) { visitor.processAttribute(this, entry.getKey()); } // traverse reference for (Map.Entry<String, JsonNode> entry : referenceNode) { visitor.processReference(this, entry.getKey()); } } /** * Detect a reference/attribute node by inspecting the _type field existence * * FIXME: based on code generation class? */ private boolean isReferenceNode(JsonNode subNode) { JsonNode node = subNode; if (subNode.isArray()) { ArrayNode array = (ArrayNode) subNode; if (array.size() > 0) { node = array.get(0); } else { node = null; } } if (node == null) { // for the empty array case, reference and attribute should have // same behavior, just return false return false; } if (null == node.get("_oid")) { return false; } return true; } public ObjectNode getNode() { return objectNode; } @Override public Date getDateField(String field) { Long l = (Long) getFieldValue(field); if (l != null) { return new Date(l); } else { return null; } } @Override public void setDateField(String fieldName, Date d) { setFieldValue(fieldName, d.getTime()); } @Override public void removeFieldValue(String fieldName) { objectNode.remove(fieldName); } @Override protected Long getLongField(String field) { Number num = (Number) getFieldValue(field); if (num instanceof Long) { return (Long) num; } else if (num != null) { return num.longValue(); } return null; } @Override protected void setLongField(String fieldName, Long d) { setFieldValue(fieldName, d); } @Override public void includeFields(String... fieldNames) { throw new UnsupportedOperationException("include fields not supported by JsonCMSEntity!"); } @Override public void excludeFields(String... fieldNames) { throw new UnsupportedOperationException("exclude fields not supported by JsonCMSEntity!"); } @Override public void clearDirtyBits() { // do nothing, json doesn't need dirty } @Override public void enableDirtyCheck() { // do nothing, json doesn't need dirty } @Override public void disableDirtyCheck() { // do nothing, json doesn't need dirty } @Override public boolean isDirty(String fieldName) { // field on json cms entity always be taken care return true; } @Override public boolean isDirtyCheckEnabled() { // json doesn't need dirty return false; } @Override public Set<String> getDirtyFields() { // json doesn't need dirty return null; } @Override public void setDirtyFields(Set<String> fields) { // do nothing, json doesn't need dirty } }