package restservices.util;
import java.util.HashMap;
import com.mendix.thirdparty.org.json.JSONObject;
import com.mendix.thirdparty.org.json.JSONArray;
import restservices.RestServices;
import com.mendix.core.Core;
import com.mendix.systemwideinterfaces.core.IDataType;
import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation;
import com.mendix.systemwideinterfaces.core.meta.IMetaAssociation.AssociationType;
import com.mendix.systemwideinterfaces.core.meta.IMetaObject;
import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive;
import com.mendix.systemwideinterfaces.core.meta.IMetaPrimitive.PrimitiveType;
public class JSONSchemaBuilder {
private IMetaObject baseObject;
private JSONObject result;
private int typeCounter = 1;
private HashMap<String, String> typeMap;
private JSONObject definitions;
private boolean hasReferenceToRoot = false;
private JSONSchemaBuilder(IMetaObject meta) {
this.baseObject = meta;
}
private JSONObject build() {
this.typeMap = new HashMap<String, String>();
this.result = new JSONObject();
this.result.put("$schema", "http://json-schema.org/draft-04/schema#");
this.definitions = new JSONObject();
buildTypeDefinition(baseObject);
if (hasReferenceToRoot)
this.result.put("$ref", "#/definitions/type1");
else {
JSONObject roottype = (JSONObject) this.definitions.remove("type1");
for(String key : JSONObject.getNames(roottype))
result.put(key, roottype.get(key));
}
//only add definitions if there are any types left.
if (definitions.keys().hasNext())
this.result.put("definitions", definitions);
return result;
}
private void buildTypeDefinition(IMetaObject meta) {
if (typeMap.containsKey(meta.getName()))
return;
typeMap.put(meta.getName(), "type" + typeCounter);
JSONObject def = new JSONObject();
definitions.put("type" + typeCounter, def);
typeCounter += 1;
def.put("type", "object");
JSONObject properties = new JSONObject();
def.put("properties", properties);
for(IMetaPrimitive prim : meta.getMetaPrimitives()) {
JSONObject type = primitiveToJSONType(prim.getType());
if (type != null)
properties.put(prim.getName(), type);
}
for(IMetaAssociation assoc : meta.getMetaAssociationsParent()) {
JSONObject type = associationToJSONType(assoc);
if (type != null)
properties.put(assoc.getName().split("\\.")[1], type);
}
}
private JSONObject associationToJSONType(IMetaAssociation assoc) {
IMetaObject child = assoc.getChild();
JSONObject type = null;
//some kind of foreign key
if (child.isPersistable()) {
//only if there is a service available for that type;
if (RestServices.getServiceForEntity(child.getName()) != null ) {
type = new JSONObject()
.put("type", "string")
.put("title", String.format("Reference to a(n) '%s'", child.getName()));
}
}
//persistent object, describe this object in the service as well
else {
buildTypeDefinition(child); //make sure the type is available in the schema
String targetType = typeMap.get(child.getName());
type = new JSONObject().put("$ref", "#/definitions/" + targetType);
if ("type1".equals(targetType))
hasReferenceToRoot = true;
}
//assoc should be included?
if (type == null)
return null;
//make sure referencesets require arrays
if (assoc.getType() == AssociationType.REFERENCESET)
type = new JSONObject().put("type", "array").put("items", type);
//make sure null refs are supported
else /* not a refset */
type = orNull(type);
return type;
}
@SuppressWarnings("deprecation")
private static JSONObject primitiveToJSONType(PrimitiveType type) {
switch(type) {
case AutoNumber:
case DateTime:
case Integer:
case Long:
return orNull(new JSONObject().put("type", "number").put("multipleOf", "1.0"));
case Binary:
return null;
case Boolean:
return orNull(new JSONObject().put("type", "boolean"));
case Currency:
case Float:
return orNull(new JSONObject().put("type", "number"));
case Enum:
return orNull(new JSONObject().put("type", "string")); //TODO: use enum from the meta model!
case HashString:
case String:
case Decimal:
return orNull(new JSONObject().put("type", "string"));
default:
throw new IllegalStateException("Unspported primitive type: " + type);
}
}
private static JSONObject orNull(JSONObject type) {
return new JSONObject().put("oneOf", new JSONArray()
.put(new JSONObject().put("type", "null"))
.put(type)
);
}
public static JSONObject build(IMetaObject meta) {
return new JSONSchemaBuilder(meta).build();
}
public static JSONObject build(IDataType type) {
if (type.isMendixObject())
return build(Core.getMetaObject(type.getObjectType()));
PrimitiveType primType = PrimitiveType.valueOf(type.getType().toString()); //MWE: WTF, there are two different types of data types?!
return primitiveToJSONType(primType);
}
}