Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #19 - Json schemafor metadata #276

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void testValidInputWithNonValidating() throws Exception {
// Emulate configuration
lbf.getJsonTranslator().setValidation(Request.class,false);

String jsonString = FileUtil.readFile("valid-deletion-req.json");
String jsonString = FileUtil.readFileAndTrim("valid-deletion-req.json");
JsonNode node = json(jsonString);
DeleteRequest req=lbf.getJsonTranslator().parse(DeleteRequest.class,node);
Assert.assertNotNull(req);
Expand All @@ -54,7 +54,7 @@ public void testInvalidInputWithNonValidating() throws Exception {
// Emulate configuration
lbf.getJsonTranslator().setValidation(Request.class,false);

String jsonString = FileUtil.readFile("invalid-deletion-req.json");
String jsonString = FileUtil.readFileAndTrim("invalid-deletion-req.json");
JsonNode node = json(jsonString);
DeleteRequest req=lbf.getJsonTranslator().parse(DeleteRequest.class,node);
Assert.assertNotNull(req);
Expand All @@ -68,7 +68,7 @@ public void testValidInputWithValidating() throws Exception {
// Emulate configuration
lbf.getJsonTranslator().setValidation(Request.class,true);

String jsonString = FileUtil.readFile("valid-deletion-req.json");
String jsonString = FileUtil.readFileAndTrim("valid-deletion-req.json");
JsonNode node = json(jsonString);
DeleteRequest req=lbf.getJsonTranslator().parse(DeleteRequest.class,node);
Assert.assertNotNull(req);
Expand All @@ -82,7 +82,7 @@ public void testInvalidInputWithValidating() throws Exception {
// Emulate configuration
lbf.getJsonTranslator().setValidation(Request.class,true);

String jsonString = FileUtil.readFile("invalid-deletion-req.json");
String jsonString = FileUtil.readFileAndTrim("invalid-deletion-req.json");
JsonNode node = json(jsonString);
try {
lbf.getJsonTranslator().parse(DeleteRequest.class,node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ public Map<MetadataRoles, List<String>> getMappedRoles() {
return null;
}

@Override
public JsonNode getJSONSchema(String entityName, String version) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}

}

private static class TestConfig extends AbstractMetadataConfiguration {
Expand All @@ -128,7 +133,7 @@ public Metadata createMetadata(DataSourcesConfiguration ds, JSONMetadataParser p
@Test
public void testExtensions() throws Exception {
// load configuration
String jsonString = FileUtil.readFile(MetadataConfiguration.FILENAME);
String jsonString = FileUtil.readFileAndTrim(MetadataConfiguration.FILENAME);
JsonNode node = json(jsonString);

// initialize config
Expand Down
1 change: 1 addition & 0 deletions config/src/test/resources/lightblue-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"metadata.find.entityNames": ["read"],
"metadata.find.entityVersions": ["read"],
"metadata.find.entityMetadata": ["read"],
"metadata.find.jsonschema": ["read"],
"metadata.insert": ["create"],
"metadata.insert.schema": ["create"],
"metadata.update.entityInfo": ["update"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ public boolean isValidForFieldType(Type fieldType) {
return true;
}

@Override
public String getDescription() {
return "test";
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@
*/
package com.redhat.lightblue.metadata;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.redhat.lightblue.metadata.constraints.IdentityConstraint;
import com.redhat.lightblue.metadata.constraints.MatchesConstraint;
import com.redhat.lightblue.metadata.constraints.MinMaxConstraint;
import com.redhat.lightblue.metadata.constraints.RequiredConstraint;
import com.redhat.lightblue.util.Path;

import java.util.*;

/**
*
Expand Down Expand Up @@ -128,4 +135,197 @@ public Map<MetadataRoles, List<String>> getMappedRoles() {
public void setRoleMap(Map<MetadataRoles, List<String>> roleMap) {
this.roleMap = roleMap;
}

@Override
public JsonNode getJSONSchema(String entityName, String version) {
ObjectNode jsonNode = new ObjectNode(JsonNodeFactory.instance);
ObjectNode propertiesNode = new ObjectNode(JsonNodeFactory.instance);
//The comments can be useful if this feature change to look like the "schema"'s json-schema
//ObjectNode versionNode = new ObjectNode(JsonNodeFactory.instance);
//ObjectNode statusNode = new ObjectNode(JsonNodeFactory.instance);
EntityMetadata entityMetadata = getEntityMetadata(entityName, version);
FieldTreeNode fieldTreeRoot = entityMetadata.getEntitySchema().getFieldTreeRoot();

jsonNode.set("$schema", TextNode.valueOf("http://json-schema.org/draft-04/schema#"));
jsonNode.set("type", TextNode.valueOf("object"));
//jsonNode.set("name", TextNode.valueOf(entityMetadata.getEntitySchema().getName()));
jsonNode.set("description", TextNode.valueOf(String.format("JSON schema for entity '%s' version '%s'", entityName, version)));
//versionNode.set("value", TextNode.valueOf(entityMetadata.getEntitySchema().getVersion().getValue()));
//versionNode.set("changelog", TextNode.valueOf(entityMetadata.getEntitySchema().getVersion().getChangelog()));
//jsonNode.set("version", versionNode);
//statusNode.set("value", TextNode.valueOf(entityMetadata.getEntitySchema().getStatus().name()));
//jsonNode.set("status", statusNode);
if(fieldTreeRoot.hasChildren()) {
jsonNode.set("properties", propertiesNode);
buildJsonNodeSchema(propertiesNode, fieldTreeRoot);
}
// Probably it would be helpful to have enums defined into the schema
/*
"enums": [
{
"name": "site_type_enum",
"values": [
"billing",
"marketing",
"service",
"shipping"
]
}
],
*/
Enums enums = entityMetadata.getEntityInfo().getEnums();
if(enums != null && !enums.isEmpty()) {
ArrayNode enumsNode = new ArrayNode(JsonNodeFactory.instance);
Set<String> keys = enums.getEnums().keySet();
for (String key : keys) {
Enum anEnum = enums.getEnum(key);
ObjectNode enumNode = new ObjectNode(JsonNodeFactory.instance);
ArrayNode valuesNode = new ArrayNode(JsonNodeFactory.instance);
enumNode.set("name", TextNode.valueOf(anEnum.getName()));
Iterator<String> iterator = anEnum.getValues().iterator();
while(iterator.hasNext()){
String next = iterator.next();
valuesNode.add(next);
}

enumNode.set("values", valuesNode);

enumsNode.add(enumNode);
}
jsonNode.set("enums", enumsNode);
}



return jsonNode;
}

private ArrayNode getRequiredFieldsArrayNode(EntityMetadata entityMetadata) {
ArrayNode value = new ArrayNode(JsonNodeFactory.instance);
Field[] requiredFields = entityMetadata.getEntitySchema().getRequiredFields();
for (Field requiredField : requiredFields) {
value.add(TextNode.valueOf(requiredField.getFullPath().toString()));
}
return value;
}

private void buildJsonNodeSchema(ObjectNode jsonNode, FieldTreeNode fieldTreeRoot) {
TreeMap<Path, Field> fieldMap = new TreeMap<>();
Iterator<? extends FieldTreeNode> children = fieldTreeRoot.getChildren();
Stack<Iterator<? extends FieldTreeNode>> fieldsPending = new Stack<>();
Stack<ObjectNode> jsonParents = new Stack<>();
Stack<ArrayNode> requiredJsonParents = new Stack<>();
ArrayNode requiredJsonNode = new ArrayNode(JsonNodeFactory.instance);
do{
FieldTreeNode fieldTreeChild = children.next();
if (fieldTreeChild instanceof ObjectField) {
fieldsPending.push(children);
jsonParents.push(jsonNode);
requiredJsonParents.push(requiredJsonNode);

ObjectField of = (ObjectField) fieldTreeChild;
ObjectNode child = new ObjectNode(JsonNodeFactory.instance);
jsonNode.set(of.getName(), child);
child.set("type", TextNode.valueOf(of.getType().getName()));

ObjectNode prop = new ObjectNode(JsonNodeFactory.instance);
child.set("properties", prop);

ObjectNode constraintsNode = new ObjectNode(JsonNodeFactory.instance);
for (FieldConstraint fc : of.getConstraints()) {
transformConstraintJsonNode(requiredJsonNode, of.getName(), child, constraintsNode, fc);
}
if(constraintsNode.size() > 0 ) {
child.set("constraints", constraintsNode);
}

jsonParents.push(child);
jsonNode = prop;
children = of.getChildren();
requiredJsonNode = new ArrayNode(JsonNodeFactory.instance);
}else if (fieldTreeChild instanceof SimpleField) {
SimpleField sf = (SimpleField) fieldTreeChild;
ObjectNode json = new ObjectNode(JsonNodeFactory.instance);
String typeString = sf.getType().getName();
if("uid".equals(typeString)){
typeString = "string";
}
json.set("type", TextNode.valueOf(typeString));
Object description = sf.getProperties().get("description");
if(description != null) {
json.set("description", TextNode.valueOf(description.toString()));
}
ObjectNode constraintsNode = new ObjectNode(JsonNodeFactory.instance);
for (FieldConstraint fc : sf.getConstraints()) {
transformConstraintJsonNode(requiredJsonNode, sf.getName(), json, constraintsNode, fc);
}
if(constraintsNode.size() > 0 ) {
json.set("constraints", constraintsNode);
}
jsonNode.set(sf.getName(),json);
}
do {
if(!children.hasNext()){
if(!fieldsPending.empty()){
children = fieldsPending.pop();
jsonNode = jsonParents.pop();
if(requiredJsonNode.size() > 0){
ArrayNode required = (ArrayNode)jsonNode.get("required");
if(required != null){
ArrayNode newNode = joinJsonNodes(requiredJsonNode, required);
jsonNode.set("required", newNode);
} else {
jsonNode.set("required", requiredJsonNode);
}
}
requiredJsonNode = requiredJsonParents.pop();
jsonNode = jsonParents.pop();
} else {
break;
}
}
} while(!children.hasNext());

} while (children.hasNext());
if(requiredJsonNode.size() > 0){
ArrayNode required = (ArrayNode)jsonNode.get("required");
if(required != null){
ArrayNode newNode = joinJsonNodes(requiredJsonNode, required);
jsonNode.set("required", newNode);
} else {
jsonNode.set("required", requiredJsonNode);
}
}
}

private ArrayNode joinJsonNodes(ArrayNode requiredJsonNode, ArrayNode required) {
ArrayNode newNode = new ArrayNode(JsonNodeFactory.instance);
Iterator<JsonNode> iterator;
Set<String> names = new TreeSet<>();

iterator = requiredJsonNode.iterator();
while (iterator.hasNext()) {
JsonNode next = iterator.next();
newNode.add(next);
names.add(next.asText());
}
iterator = required.iterator();
while (iterator.hasNext()) {
JsonNode next = iterator.next();
if (!names.contains(next.asText())) {
newNode.add(next);
}
}
return newNode;
}

private void transformConstraintJsonNode(ArrayNode requiredJsonNode, String name, ObjectNode json, ObjectNode constraintsNode, FieldConstraint fc) {
if (fc instanceof IdentityConstraint || fc instanceof RequiredConstraint) {
requiredJsonNode.add(name);
} else if (fc instanceof MatchesConstraint) {
json.set("pattern" , TextNode.valueOf(fc.getDescription()));
} else {
constraintsNode.set(fc.getType() , TextNode.valueOf(fc.getDescription()));
}
}
}
Loading