package restservices.util; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.mendix.thirdparty.org.json.JSONArray; import com.mendix.thirdparty.org.json.JSONException; import com.mendix.thirdparty.org.json.JSONObject; import restservices.RestServices; import restservices.consume.RestConsumer; import restservices.proxies.HttpMethod; import restservices.proxies.Primitive; import restservices.proxies.RestPrimitiveType; import restservices.proxies.BooleanValue; import com.mendix.core.Core; import com.mendix.core.objectmanagement.member.MendixObjectReference; import com.mendix.core.objectmanagement.member.MendixObjectReferenceSet; import com.mendix.systemwideinterfaces.core.IContext; import com.mendix.systemwideinterfaces.core.IMendixIdentifier; import com.mendix.systemwideinterfaces.core.IMendixObject; import com.mendix.systemwideinterfaces.core.IMendixObjectMember; import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation; import com.mendix.systemwideinterfaces.core.meta.IMetaObject; import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive; import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive.PrimitiveType; public class JsonDeserializer { public static IMendixIdentifier readJsonDataIntoMendixObject(IContext context, Object jsonValue, String targetType, boolean autoResolveReferences) throws Exception { IMendixObject target = Core.instantiate(context, targetType); readJsonDataIntoMendixObject(context, jsonValue, target, autoResolveReferences); return target.getId(); } public static void readJsonDataIntoMendixObject(IContext context, Object jsonValue, IMendixObject target, boolean autoResolveReferences) throws Exception { if (!Utils.hasDataAccess(target.getMetaObject(), context)) throw new IllegalStateException("During JSON deserialization: Object of type '" + target.getType() + "' cannot be altered by users with role(s) " + context.getSession().getUserRolesNames() + ". Please check the security rules"); String targetType = target.getType(); //primitive if (Core.isSubClassOf(Primitive.entityName, targetType)) { Primitive prim = Primitive.initialize(context, target); prim.setStringValue(String.valueOf(jsonValue)); if (jsonValue == null || jsonValue == JSONObject.NULL) prim.setPrimitiveType(RestPrimitiveType._NULL); else if (jsonValue instanceof String) prim.setPrimitiveType(RestPrimitiveType.String); else if (jsonValue instanceof Boolean) { prim.setBooleanValue((Boolean) jsonValue); prim.setPrimitiveType(RestPrimitiveType._Boolean); } else if (jsonValue instanceof Long || jsonValue instanceof Double || jsonValue instanceof Integer || jsonValue instanceof Float) { prim.setPrimitiveType(RestPrimitiveType.Number); prim.setNumberValue(new BigDecimal(jsonValue.toString())); } else throw new RuntimeException("Unable to convert value of type '" + jsonValue.getClass().getName()+ "' to rest primitive: " + jsonValue.toString()); } //string; autoresolve else if (jsonValue instanceof String) { if (!autoResolveReferences) throw new RuntimeException("Unable to read url '" + jsonValue + "' into '" + targetType + "'; since references will not be resolved automatically for incoming data"); RestConsumer.request(context, HttpMethod.GET, (String) jsonValue, null, target, false); } else if (jsonValue instanceof JSONObject) { readJsonObjectIntoMendixObject(context, (JSONObject) jsonValue, target, autoResolveReferences); } else throw new RuntimeException("Unable to parse '" + jsonValue.toString() + "' into '" + targetType + "'"); } private static void readJsonObjectIntoMendixObject(IContext context, JSONObject object, IMendixObject target, boolean autoResolve) throws JSONException, Exception { Iterator<String> it = object.keys(); Map<String, String> attributeNameMap = buildAttributeNameMap(target.getMetaObject()); while(it.hasNext()) { String attr = it.next(); String targetattr = attributeNameMap.get(attr.toLowerCase().replaceAll("[^a-zA-Z0-9_]","_")); if (targetattr == null) { if (RestServices.LOGUTIL.isDebugEnabled()) RestServices.LOGUTIL.debug("Skipping attribute '" + attr + "', not found in targettype: '" + target.getType() + "'"); continue; } IMendixObjectMember<?> member = target.getMember(context, targetattr); if (member.isVirtual()) continue; //Reference else if (member instanceof MendixObjectReference) { String otherSideType = target.getMetaObject().getMetaAssociationParent(targetattr).getChild().getName(); if (!object.isNull(attr)) ((MendixObjectReference)member).setValue(context, readJsonDataIntoMendixObject(context, object.get(attr), otherSideType, autoResolve)); } //ReferenceSet else if (member instanceof MendixObjectReferenceSet){ String otherSideType = target.getMetaObject().getMetaAssociationParent(targetattr).getChild().getName(); JSONArray children = object.getJSONArray(attr); List<IMendixIdentifier> ids = new ArrayList<IMendixIdentifier>(); for(int i = 0; i < children.length(); i++) { IMendixIdentifier child = readJsonDataIntoMendixObject(context, children.get(i), otherSideType, autoResolve); if (child != null) { /* * The core.createMendixIdentifier should be unnecessary, however, there is a bug there, see * support ticket 102188 */ ids.add(Core.createMendixIdentifier(child.toLong())); } } ((MendixObjectReferenceSet)member).setValue(context, ids); } //Primitive member else if (target.hasMember(targetattr)){ IMetaPrimitive primitive = target.getMetaObject().getMetaPrimitive(targetattr); if (primitive.getType() != PrimitiveType.AutoNumber) target.setValue(context, targetattr, jsonAttributeToPrimitive(primitive, object, attr)); } } Core.commit(context, target); } private static final Map<String, Map<String,String>> metaAttributeMaps = new HashMap<String, Map<String, String>>(); private static Map<String, String> buildAttributeNameMap(IMetaObject metaObject) { if (metaAttributeMaps.containsKey(metaObject.getName())) return metaAttributeMaps.get(metaObject.getName()); Map<String, String> attrMap = new HashMap<String,String>(); for(IMetaAssociation assoc : metaObject.getMetaAssociationsParent()) { String name = assoc.getName().split("\\.")[1]; attrMap.put(name.toLowerCase(), assoc.getName()); if (name.startsWith("_")) attrMap.put(name.substring(1).toLowerCase(), assoc.getName()); } for(IMetaPrimitive prim : metaObject.getMetaPrimitives()) { String name = prim.getName(); attrMap.put(name.toLowerCase(), name); if (name.startsWith("_")) attrMap.put(name.substring(1).toLowerCase(), name); } metaAttributeMaps.put(metaObject.getName(), attrMap); return attrMap; } @SuppressWarnings("deprecation") private static Object jsonAttributeToPrimitive(IMetaPrimitive primitive, JSONObject object, String attr) throws Exception { switch(primitive.getType()) { case Currency: case Float: if (object.isNull(attr)) return null; return object.getDouble(attr); case Decimal: if (object.isNull(attr)) return null; String asString = object.optString(attr, null); if (asString != null) return new BigDecimal(asString); return new BigDecimal(object.getDouble(attr)); case Boolean: return object.getBoolean(attr); case DateTime: if (object.isNull(attr)) return null; return new Date(object.getLong(attr)); case Enum: // support for built-in BooleanValue enumeration if ("RestServices.BooleanValue".equals(primitive.getEnumeration().getName())) { if(object.isNull(attr)) return null; return object.getBoolean(attr) ? BooleanValue._true.toString() : BooleanValue._false.toString(); } // fall-through intentional case HashString: case String: if (object.isNull(attr)) return null; return object.getString(attr); case AutoNumber: case Long: if (object.isNull(attr)) return null; return object.getLong(attr); case Integer: if (object.isNull(attr)) return null; return object.getInt(attr); case Binary: default: throw new Exception("Unsupported attribute type '" + primitive.getType() + "' in attribute '" + attr + "'"); } } }