package restservices.util;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.mendix.thirdparty.org.json.JSONArray;
import com.mendix.thirdparty.org.json.JSONObject;
import restservices.RestServices;
import restservices.proxies.BooleanValue;
import restservices.proxies.Primitive;
import restservices.publish.DataService;
import com.mendix.core.Core;
import com.mendix.core.objectmanagement.member.MendixEnum;
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.IMetaObject;
public class JsonSerializer {
/**
* returns a json string containingURL if id is persistable or json object if with the json representation if the object is not. s
* @param rsr
* @param id
* @param useServiceUrls
* @return
* @throws Exception
*/
public static Object identifierToJSON(IContext context, IMendixIdentifier id, boolean useServiceUrls) throws Exception {
return identifierToJSON(context, id, new HashSet<Long>(), useServiceUrls);
}
private static Object identifierToJSON(IContext context, IMendixIdentifier id, Set<Long> alreadySeen, boolean useServiceUrls) throws Exception {
if (id == null)
return null;
if (alreadySeen.contains(id.toLong())) {
RestServices.LOGUTIL.warn("ID already seen: " + id.toLong() + ", skipping serialization");
return null;
}
alreadySeen.add(id.toLong());
/* persistable object, generate url */
if (Core.getMetaObject(id.getObjectType()).isPersistable()) {
if (!useServiceUrls)
return null;
DataService service = RestServices.getServiceForEntity(id.getObjectType());
if (service == null) {
RestServices.LOGUTIL.warn("No RestService has been definied for type: " + id.getObjectType() + ", identifier could not be serialized");
return null;
}
IMendixObject obj = Core.retrieveId(context, id); //Optimize: for refset use retrieve ids
if (obj == null) {
RestServices.LOGUTIL.warn("Failed to retrieve identifier: " + id + ", does the object still exist?");
return null;
}
if (Utils.isValidKey(service.getKey(context, obj)))
return service.getObjecturl(context, obj);
return null;
}
/* Non persistable object, write the object itself */
IMendixObject obj = Core.retrieveId(context, id); //Optimize: for refset use retrieve ids
if (obj == null) {
RestServices.LOGUTIL.warn("Failed to retrieve identifier: " + id + ", does the object still exist?");
return null;
}
else if (obj.getType().equals(Primitive.entityName)) {
return writePrimitiveToJson(context, Primitive.initialize(context, obj));
}
else
return writeMendixObjectToJson(context, obj, alreadySeen, useServiceUrls);
}
private static Object writePrimitiveToJson(IContext context, Primitive primitive) {
if (primitive.getPrimitiveType() == null)
throw new IllegalStateException("PrimitiveType attribute of RestServices.Primitive should be set");
switch (primitive.getPrimitiveType()) {
case Number:
return primitive.getNumberValue();
case String:
return primitive.getStringValue();
case _NULL:
return JSONObject.NULL;
case _Boolean:
return primitive.getBooleanValue();
default:
throw new IllegalStateException("PrimitiveType attribute of RestServices.Primitive should be set");
}
}
public static JSONObject writeMendixObjectToJson(IContext context, IMendixObject view) throws Exception {
return writeMendixObjectToJson(context, view, false);
}
public static JSONObject writeMendixObjectToJson(IContext context, IMendixObject view, boolean useServiceUrls) throws Exception {
return writeMendixObjectToJson(context, view, new HashSet<Long>(), useServiceUrls);
}
private static JSONObject writeMendixObjectToJson(IContext context, IMendixObject view, Set<Long> alreadySeen, boolean useServiceUrls) throws Exception {
if (view == null)
throw new IllegalArgumentException("Mendix to JSON conversion expects an object");
if (!Utils.hasDataAccess(view.getMetaObject(), context))
throw new IllegalStateException("During JSON serialization: Object of type '" + view.getType() + "' has no readable members for users with role(s) " + context.getSession().getUserRolesNames() + ". Please check the security rules");
JSONObject res = new JSONObject();
Map<String, ? extends IMendixObjectMember<?>> members = view.getMembers(context);
for(java.util.Map.Entry<String, ? extends IMendixObjectMember<?>> e : members.entrySet())
serializeMember(context, res, getTargetMemberName(context, view, e.getKey()), e.getValue(), view.getMetaObject(), alreadySeen, useServiceUrls);
return res;
}
private static String getTargetMemberName(IContext context,
IMendixObject view, String sourceAttr) {
String name = Utils.getShortMemberName(sourceAttr);
if (view.hasMember(name + "_jsonkey"))
name = (String) view.getValue(context, name + "_jsonkey");
if (name == null || name.trim().isEmpty())
throw new IllegalStateException("During JSON serialization: Object of type '" + view.getType() + "', member '" + sourceAttr + "' has a corresponding '_jsonkey' attribute, but its value is empty.");
return name;
}
@SuppressWarnings("deprecation")
private static void serializeMember(IContext context, JSONObject target, String targetMemberName,
IMendixObjectMember<?> member, IMetaObject viewType, Set<Long> alreadySeen, boolean useServiceUrls) throws Exception {
if (context == null)
throw new IllegalStateException("Context is null");
Object value = member.getValue(context);
String memberName = member.getName();
if (Utils.isSystemAttribute(memberName) || memberName.endsWith("_jsonkey")) {
//skip
}
//Primitive?
else if (!(member instanceof MendixObjectReference) && !(member instanceof MendixObjectReferenceSet)) {
switch(viewType.getMetaPrimitive(member.getName()).getType()) {
case AutoNumber:
case Long:
case Boolean:
case Currency:
case Float:
case Integer:
if (value == null) {
// Numbers or bools could be null in json, technically.
// Mendix supports it as well. Technically.
RestServices.LOGUTIL.warn("Got 'null' as value for primitive '" + targetMemberName + "'");
target.put(targetMemberName, JSONObject.NULL);
} else {
target.put(targetMemberName, value);
}
break;
case Enum:
//Support for built-in BooleanValue enumeration.
MendixEnum me = (MendixEnum) member;
if ("RestServices.BooleanValue".equals(me.getEnumeration().getName())) {
if (BooleanValue._true.toString().equals(me.getValue(context)))
target.put(targetMemberName, true);
else if (BooleanValue._false.toString().equals(me.getValue(context)))
target.put(targetMemberName, false);
break;
}
//other enumeration, fall trough intentional
case HashString:
case String:
if (value == null)
target.put(targetMemberName, JSONObject.NULL);
else
target.put(targetMemberName, value);
break;
case Decimal:
if (value == null)
target.put(targetMemberName, JSONObject.NULL);
else
target.put(targetMemberName, value.toString());
break;
case DateTime:
if (value == null)
target.put(targetMemberName, JSONObject.NULL);
else
target.put(targetMemberName, (((Date)value).getTime()));
break;
case Binary:
break;
default:
throw new IllegalStateException("Not supported Mendix Membertype for member " + memberName);
}
}
/**
* Reference
*/
else if (member instanceof MendixObjectReference){
if (value != null)
value = identifierToJSON(context, (IMendixIdentifier) value, alreadySeen, useServiceUrls);
if (value == null)
target.put(targetMemberName, JSONObject.NULL);
else
target.put(targetMemberName, value);
}
/**
* Referenceset
*/
else if (member instanceof MendixObjectReferenceSet){
JSONArray ar = new JSONArray();
if (value != null) {
@SuppressWarnings("unchecked")
List<IMendixIdentifier> ids = (List<IMendixIdentifier>) value;
Utils.sortIdList(ids);
for(IMendixIdentifier id : ids) if (id != null) {
Object url = identifierToJSON(context, id, alreadySeen, useServiceUrls);
if (url != null)
ar.put(url);
}
}
target.put(targetMemberName, ar);
}
else
throw new IllegalStateException("Unimplemented membertype " + member.getClass().getSimpleName());
}
}