From a6875c8dab561180eb635fe096f9496400b0797c Mon Sep 17 00:00:00 2001 From: Luan Cestari Date: Thu, 8 Jan 2015 18:48:42 -0200 Subject: [PATCH 1/7] First changes to make JSON document of the entity fields --- .../config/MetadataConfigurationTest.java | 5 + .../lightblue/metadata/AbstractMetadata.java | 72 +++- .../lightblue/metadata/EntitySchema.java | 196 +++++---- .../redhat/lightblue/metadata/Metadata.java | 7 + .../metadata/parser/MetadataParser.java | 5 + .../metadata/RequiredFieldsTest.java | 72 ++++ .../metadata/test/DatabaseMetadata.java | 6 + metadata/src/test/resources/usermd.json | 389 +++++++++--------- 8 files changed, 482 insertions(+), 270 deletions(-) create mode 100644 metadata/src/test/java/com/redhat/lightblue/metadata/RequiredFieldsTest.java diff --git a/config/src/test/java/com/redhat/lightblue/config/MetadataConfigurationTest.java b/config/src/test/java/com/redhat/lightblue/config/MetadataConfigurationTest.java index e77049e4..5d9c1e1e 100644 --- a/config/src/test/java/com/redhat/lightblue/config/MetadataConfigurationTest.java +++ b/config/src/test/java/com/redhat/lightblue/config/MetadataConfigurationTest.java @@ -113,6 +113,11 @@ public Map> 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 { diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java index d64bc720..181062f6 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java @@ -18,11 +18,14 @@ */ 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.util.Path; + +import java.util.*; /** * @@ -128,4 +131,63 @@ public Map> getMappedRoles() { public void setRoleMap(Map> roleMap) { this.roleMap = roleMap; } + + @Override + public JsonNode getJSONSchema(String entityName, String version) { + ObjectNode jsonNode = new ObjectNode(JsonNodeFactory.instance); + ObjectNode properties = 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("description", TextNode.valueOf(String.format("JSON schema for entity '%s' version '%s'", entityName, version))); + jsonNode.set("properties", properties); + buildJsonNodeSchema(properties, fieldTreeRoot); + ArrayNode value = new ArrayNode(JsonNodeFactory.instance); + Field[] requiredFields = entityMetadata.getEntitySchema().getRequiredFields(); + for (Field requiredField : requiredFields) { + TextNode.valueOf(requiredField.getFullPath().toString()); + } + properties.set("required", value); + + return jsonNode; + } + + private void buildJsonNodeSchema(ObjectNode jsonNode, FieldTreeNode fieldTreeRoot) { + TreeMap fieldMap = new TreeMap<>(); + Iterator children = fieldTreeRoot.getChildren(); + Stack> fieldsPending = new Stack<>(); + do{ + FieldTreeNode fieldTreeChild = children.next(); + if (fieldTreeChild instanceof ObjectField) { + ObjectField of = (ObjectField) fieldTreeChild; + ///// + fieldsPending.push(children); + children = of.getChildren(); + }else if (fieldTreeChild instanceof SimpleField) { + SimpleField sf = (SimpleField) fieldTreeChild; + ObjectNode json = new ObjectNode(JsonNodeFactory.instance); + json.set("type", TextNode.valueOf(sf.getType().getName())); + //json.set("type", TextNode.valueOf(sf.getProperties().getName())); + + + for (FieldConstraint fc : sf.getConstraints()) { + + } + jsonNode.set(sf.getName(),json); + } + do { + if(!children.hasNext()){ + if(!fieldsPending.empty()){ + children = fieldsPending.pop(); + } else { + break; + } + } + } while(!children.hasNext()); + + } while (children.hasNext()); + + } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/EntitySchema.java b/metadata/src/main/java/com/redhat/lightblue/metadata/EntitySchema.java index 7c416e72..51f9caee 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/EntitySchema.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/EntitySchema.java @@ -19,6 +19,7 @@ package com.redhat.lightblue.metadata; import com.redhat.lightblue.metadata.constraints.IdentityConstraint; +import com.redhat.lightblue.metadata.constraints.RequiredConstraint; import com.redhat.lightblue.util.Error; import com.redhat.lightblue.util.MutablePath; import com.redhat.lightblue.util.Path; @@ -39,67 +40,17 @@ public class EntitySchema implements Serializable { private static final long serialVersionUID = 1l; private final String name; - private Version version; - private MetadataStatus status; private final ArrayList statusChangeLog; //hooks private final EntityAccess access; private final ArrayList constraints; + private final Map properties; + private Version version; + private MetadataStatus status; private Fields fields; private FieldTreeNode fieldRoot; - private final Map properties; - - protected class RootNode implements FieldTreeNode, Serializable { - - private static final long serialVersionUID = 1L; - - @Override - public String getName() { - return ""; - } - @Override - public Type getType() { - return null; - } - - @Override - public boolean hasChildren() { - return true; - } - - @Override - public Iterator getChildren() { - return fields.getFields(); - } - - @Override - public FieldTreeNode resolve(Path p) { - return fields.resolve(p); - } - - @Override - public FieldTreeNode resolve(Path p, int level) { - return fields.resolve(p, level); - } - - @Override - public FieldTreeNode getParent() { - return null; - } - - @Override - public Path getFullPath() { - return Path.EMPTY; - } - - @Override - public MutablePath getFullPath(MutablePath mp) { - return Path.EMPTY.mutableCopy(); - } - }; - - public EntitySchema(String name) { + public EntitySchema(String name) { this.name = name; this.fieldRoot = new RootNode(); this.fields = new Fields(fieldRoot); @@ -107,7 +58,7 @@ public EntitySchema(String name) { this.access = new EntityAccess(); this.constraints = new ArrayList<>(); this.properties = new HashMap<>(); - } + }; /** * Copy ctor with shallow copy @@ -269,34 +220,125 @@ public Map getProperties() { } public Field[] getIdentityFields() { - FieldCursor cursor = getFieldCursor(); - TreeMap fieldMap = new TreeMap<>(); - getIdentityFields(fieldMap, cursor); - Field[] ret = new Field[fieldMap.size()]; - int i = 0; - for (Field f : fieldMap.values()) { - ret[i++] = f; - } + TreeMap fieldMap = filterFieldsByConstraints(new FilterConstraint() { + @Override public boolean filter(FieldTreeNode ftn, SimpleField sf, FieldConstraint fc, TreeMap fieldMap) { + if (fc instanceof IdentityConstraint) { + fieldMap.put(sf.getFullPath(), sf); + return true; + } + return false; + } + }); + Field[] ret = fieldMap.values().toArray(new Field[fieldMap.size()]); return ret; } - private void getIdentityFields(TreeMap fieldMap, FieldCursor cursor) { - if (cursor.firstChild()) { + public Field[] getRequiredFields() { + TreeMap fieldMap = filterFieldsByConstraints(new FilterConstraint() { + @Override public boolean filter(FieldTreeNode ftn, SimpleField sf, FieldConstraint fc, TreeMap fieldMap) { + if (fc instanceof RequiredConstraint) { + RequiredConstraint rc = (RequiredConstraint)fc; + if(rc.getValue()) { + fieldMap.put(sf.getFullPath(), sf); + return true; + } + } + return false; + } + }); + Field[] ret = fieldMap.values().toArray(new Field[fieldMap.size()]); + return ret; + } + + public TreeMap filterFieldsByConstraints(FilterConstraint filterConstraint) { + TreeMap fieldMap = new TreeMap<>(); + FieldTreeNode fieldTreeRoot = getFieldTreeRoot(); + Iterator children = fieldTreeRoot.getChildren(); + Stack> fieldsPending = new Stack<>(); + do{ + FieldTreeNode fieldTreeChild = children.next(); + if (fieldTreeChild instanceof ObjectField) { + ObjectField of = (ObjectField) fieldTreeChild; + fieldsPending.push(children); + children = of.getChildren(); + }else if (fieldTreeChild instanceof SimpleField) { + SimpleField f = (SimpleField) fieldTreeChild; + for (FieldConstraint fc : f.getConstraints()) { + boolean stop = filterConstraint.filter(fieldTreeChild, f, fc, fieldMap); + if(stop){ + break; + } + } + } do { - FieldTreeNode fn = cursor.getCurrentNode(); - if (fn instanceof ObjectField) { - getIdentityFields(fieldMap, cursor); - } else if (fn instanceof SimpleField) { - SimpleField f = (SimpleField) fn; - for (FieldConstraint fc : f.getConstraints()) { - if (fc instanceof IdentityConstraint) { - fieldMap.put(f.getFullPath(), f); - break; - } + if(!children.hasNext()){ + if(!fieldsPending.empty()){ + children = fieldsPending.pop(); + } else { + break; } } - } while (cursor.nextSibling()); - cursor.parent(); + } while(!children.hasNext()); + + } while (children.hasNext()); + + return fieldMap; + } + + public interface FilterConstraint{ + /** + * @return break the iteration of FieldConstraint items + */ + boolean filter(FieldTreeNode ftn, SimpleField sf, FieldConstraint fc, TreeMap fieldMap); + } + +protected class RootNode implements FieldTreeNode, Serializable { + + private static final long serialVersionUID = 1L; + + @Override + public String getName() { + return ""; + } + + @Override + public Type getType() { + return null; + } + + @Override + public boolean hasChildren() { + return true; + } + + @Override + public Iterator getChildren() { + return fields.getFields(); + } + + @Override + public FieldTreeNode resolve(Path p) { + return fields.resolve(p); + } + + @Override + public FieldTreeNode resolve(Path p, int level) { + return fields.resolve(p, level); + } + + @Override + public FieldTreeNode getParent() { + return null; + } + + @Override + public Path getFullPath() { + return Path.EMPTY; + } + + @Override + public MutablePath getFullPath(MutablePath mp) { + return Path.EMPTY.mutableCopy(); } } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/Metadata.java b/metadata/src/main/java/com/redhat/lightblue/metadata/Metadata.java index d26b2ab6..9e41182c 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/Metadata.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/Metadata.java @@ -22,7 +22,9 @@ import java.util.List; import java.util.Map; +import com.fasterxml.jackson.databind.JsonNode; import com.redhat.lightblue.Response; +import org.json.JSONObject; /** * Metadata manager interface @@ -110,4 +112,9 @@ void setMetadataStatus(String entityName, */ Map> getMappedRoles(); + + /** + * Returns JSON node that represents Schema for Metadata + */ + JsonNode getJSONSchema(String entityName, String version); } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/parser/MetadataParser.java b/metadata/src/main/java/com/redhat/lightblue/metadata/parser/MetadataParser.java index 41236177..7cdd6a1e 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/parser/MetadataParser.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/parser/MetadataParser.java @@ -761,6 +761,11 @@ private Field parseField(String name, T object) { parseFieldConstraints(field, getObjectProperty(object, STR_CONSTRAINTS)); + String stringProperty = getStringProperty(object, STR_DESCRIPTION); + if(stringProperty != null && !stringProperty.isEmpty()) { + field.getProperties().put(STR_DESCRIPTION, stringProperty); + } + parsePropertyParser(object, field.getProperties()); } else { field = null; diff --git a/metadata/src/test/java/com/redhat/lightblue/metadata/RequiredFieldsTest.java b/metadata/src/test/java/com/redhat/lightblue/metadata/RequiredFieldsTest.java new file mode 100644 index 00000000..37b637fc --- /dev/null +++ b/metadata/src/test/java/com/redhat/lightblue/metadata/RequiredFieldsTest.java @@ -0,0 +1,72 @@ +/* + Copyright 2013 Red Hat, Inc. and/or its affiliates. + + This file is part of lightblue. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +package com.redhat.lightblue.metadata; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.redhat.lightblue.metadata.parser.DataStoreParser; +import com.redhat.lightblue.metadata.parser.Extensions; +import com.redhat.lightblue.metadata.parser.JSONMetadataParser; +import com.redhat.lightblue.metadata.parser.MetadataParser; +import com.redhat.lightblue.metadata.types.DefaultTypes; +import com.redhat.lightblue.util.JsonUtils; +import org.junit.Assert; +import org.junit.Test; + +public class RequiredFieldsTest { + + private static final JsonNodeFactory nodeFactory = JsonNodeFactory.withExactBigDecimals(false); + + @Test + public void userTest1() throws Exception { + Extensions extensions = new Extensions<>(); + extensions.addDefaultExtensions(); + extensions.registerDataStoreParser("mongo", new DataStoreParser() { + @Override + public String getDefaultName() { + return "mongo"; + } + + @Override + public DataStore parse(String name, MetadataParser p, JsonNode node) { + return new DataStore() { + @Override + public String getBackend() { + return "mongo"; + } + }; + } + + @Override + public void convert(MetadataParser p, JsonNode emptyNode, DataStore object) { + } + }); + JSONMetadataParser parser = new JSONMetadataParser(extensions, new DefaultTypes(), nodeFactory); + + JsonNode node = JsonUtils.json(getClass().getResourceAsStream("/usermd.json")); + EntityMetadata md = parser.parseEntityMetadata(node); + + Field[] rf = md.getEntitySchema().getRequiredFields(); + Assert.assertEquals(3, rf.length); + Assert.assertTrue("login".equals(rf[0].getName()) || "login".equals(rf[1].getName()) || "login".equals(rf[2].getName())); + Assert.assertTrue("requid".equals(rf[0].getName()) || "requid".equals(rf[1].getName()) || "requid".equals(rf[2].getName())); + Assert.assertTrue("requid".equals(rf[0].getName()) || "requid".equals(rf[1].getName()) || "requid".equals(rf[2].getName())); + Assert.assertTrue((!rf[0].equals(rf[1])) && (!rf[1].equals(rf[2])) ); + } +} diff --git a/metadata/src/test/java/com/redhat/lightblue/metadata/test/DatabaseMetadata.java b/metadata/src/test/java/com/redhat/lightblue/metadata/test/DatabaseMetadata.java index 32239d1c..d38bba28 100644 --- a/metadata/src/test/java/com/redhat/lightblue/metadata/test/DatabaseMetadata.java +++ b/metadata/src/test/java/com/redhat/lightblue/metadata/test/DatabaseMetadata.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; +import com.fasterxml.jackson.databind.JsonNode; import com.redhat.lightblue.Response; import com.redhat.lightblue.metadata.EntityInfo; import com.redhat.lightblue.metadata.EntityMetadata; @@ -100,4 +101,9 @@ public void removeEntity(String entityName) { public Map> getMappedRoles() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } + + @Override + public JsonNode getJSONSchema(String entityName, String version) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } } diff --git a/metadata/src/test/resources/usermd.json b/metadata/src/test/resources/usermd.json index f4f61470..53ab47ba 100644 --- a/metadata/src/test/resources/usermd.json +++ b/metadata/src/test/resources/usermd.json @@ -1,209 +1,222 @@ { - "entityInfo" : { + "entityInfo": { "name": "user", - "enums" : [ - { - "name":"site_type_enum", - "values": [ "billing", "marketing", "service", "shipping" ] - } + "enums": [ + { + "name": "site_type_enum", + "values": [ + "billing", + "marketing", + "service", + "shipping" + ] + } ], "datastore": { - "backend":"mongo", - "datasource": "mongodata", - "collection": "user" + "backend": "mongo", + "datasource": "mongodata", + "collection": "user" } }, - "schema" : { - "name" : "user", + "schema": { + "name": "user", "version": { - "value": "5.0.0", - "changelog": "UID Test version" + "value": "5.0.0", + "changelog": "UID Test version" }, "status": { - "value": "active" + "value": "active" }, - "access" : { - "insert": ["anyone"], - "find":["anyone"], - "update":["anyone"], - "delete":["anyone"] + "access": { + "insert": ["anyone"], + "find": ["anyone"], + "update": ["anyone"], + "delete": ["anyone"] }, "fields": { - "_id": {"type": "string", - "constraints" : { - "identity":1 - }}, - "objectType": {"type": "string"}, - "login": { + "_id": { + "type": "string", + "constraints": { + "identity": 1 + } + }, + "objectType": {"type": "string"}, + "login": { + "type": "string", + "constraints": { + "maxLength": 64, + "minLength": 1, + "required": true + } + }, + "requid": { + "type": "uid", + "description": "test", + "constraints": { + "required": true + } + }, + "nonrequid": { + "type": "uid", + "constraints": { + "required": false + } + }, + "iduid": { + "type": "uid", + "constraints": { + "identity": true + } + }, + "password": { + "type": "object", + "fields": { + "hashed": {"type": "string"}, + "salt": {"type": "string"} + } + }, + "active": {"type": "boolean"}, + "contactPermissions": { + "type": "object", + "fields": { + "allowEmailContact": {"type": "boolean"}, + "allowFaxContact": {"type": "boolean"}, + "allowMailContact": {"type": "boolean"}, + "allowPhoneContact": {"type": "boolean"}, + "allowThirdPartyContact": {"type": "boolean"} + } + }, + "personalInfo": { + "type": "object", + "fields": { + "company": {"type": "string"}, + "greeting": {"type": "string"}, + "firstName": {"type": "string"}, + "lastName": {"type": "string"}, + "suffix": {"type": "string"}, + "title": {"type": "string"}, + "email": {"type": "string"}, + "emailConfirmed": {"type": "boolean"}, + "phoneNumber": { "type": "string", "constraints": { - "maxLength": 64, - "minLength": 1, - "required": true - } - }, - "requid" : { - "type":"uid", - "constraints" : { - "required":true - } - }, - "nonrequid" : { - "type":"uid", - "constraints" : { - "required":false - } - }, - "iduid" : { - "type":"uid", - "constraints" : { - "identity":true - } - }, - "password": { - "type": "object", - "fields": { - "hashed": {"type": "string"}, - "salt": {"type": "string"} + "matches": "^\\d{3}\\-\\d{4}\\ \\d{4}$" } - }, - "active" : {"type": "boolean" }, - "contactPermissions" : { - "type":"object", - "fields" : { - "allowEmailContact" : {"type":"boolean"}, - "allowFaxContact" : {"type":"boolean"}, - "allowMailContact" : {"type":"boolean"}, - "allowPhoneContact" : {"type":"boolean"}, - "allowThirdPartyContact" : {"type":"boolean"} - } - }, - "personalInfo" : { - "type" : "object", - "fields" : { - "company": {"type": "string"}, - "greeting": {"type": "string"}, - "firstName": {"type": "string"}, - "lastName": {"type": "string"}, - "suffix": {"type": "string"}, - "title": {"type": "string"}, - "email": {"type": "string"}, - "emailConfirmed": {"type": "boolean"}, - "phoneNumber": {"type": "string", - "constraints" : { - "matches":"^\\d{3}\\-\\d{4}\\ \\d{4}$" - } - }, - "faxNumber": {"type": "string"}, - "locale": {"type": "string"}, - "timezone": {"type": "string"}, - "department": {"type": "string"}, - "requid" : { - "type":"uid", - "constraints" : { - "required":true + }, + "faxNumber": {"type": "string"}, + "locale": {"type": "string"}, + "timezone": {"type": "string"}, + "department": {"type": "string"}, + "requid": { + "type": "uid", + "constraints": { + "required": true } - }, - "nonrequid" : { - "type":"uid", - "constraints" : { - "required":false + }, + "nonrequid": { + "type": "uid", + "constraints": { + "required": false } + } } + }, + "sites": { + "type": "array", + "items": { + "type": "object", + "fields": { + "siteId": {"type": "string"}, + "streetAddress": { + "type": "object", + "fields": { + "requid": { + "type": "uid", + "constraints": { + "required": true + } + }, + "nonrequid": { + "type": "uid", + "constraints": { + "required": false + } + }, + "address1": {"type": "string"}, + "address2": {"type": "string"}, + "address3": {"type": "string"}, + "address4": {"type": "string"}, + "city": {"type": "string"}, + "state": {"type": "string"}, + "country": {"type": "string"}, + "countryCode": {"type": "string"}, + "postalCode": {"type": "string"}, + "poBox": {"type": "boolean"} } - }, - "sites" : { - "type":"array", - "items" : { - "type":"object", - "fields" : { - "siteId" : { "type":"string" }, - "streetAddress" : { - "type" : "object", - "fields" : { - "requid" : { - "type":"uid", - "constraints" : { - "required":true - } - }, - "nonrequid" : { - "type":"uid", - "constraints" : { - "required":false - } - }, - "address1":{"type":"string"}, - "address2":{"type":"string"}, - "address3":{"type":"string"}, - "address4":{"type":"string"}, - "city":{"type":"string"}, - "state":{"type":"string"}, - "country":{"type":"string"}, - "countryCode":{"type":"string"}, - "postalCode":{"type":"string"}, - "poBox":{"type":"boolean"} - } - }, - "contactInfo": { - "type" :"object", - "fields": { - "emailAddress":{"type":"string"}, - "faxNumber": {"type":"string"}, - "phoneNumber" : {"type":"string", - "constraints" : { - "matches":"^\\d{3}\\-\\d{4}\\ \\d{4}$" - } - }, - "url":{"type":"string"} - } - }, - "notes":{"type":"string"}, - "siteType":{ - "type":"string", - "constraints": [ - {"enum":"site_type_enum"} - ] - }, - "firstName":{"type":"string"}, - "lastName":{"type":"string"}, - "description":{"type":"string"}, - "defaultSite":{"type":"boolean"}, - "active":{"type":"boolean"}, - "usages": { - "type":"array", - "items": { - "type":"object", - "fields": { - "requid" : { - "type":"uid", - "constraints" : { - "required":true - } - }, - "nonrequid" : { - "type":"uid", - "constraints" : { - "required":false - } - }, - "usage":{ - "type":"string", - "constraints": [ - {"enum":"site_type_enum"} - ] - }, - "lastUsedOn":{"type":"date"} - } - } - } - } + }, + "contactInfo": { + "type": "object", + "fields": { + "emailAddress": {"type": "string"}, + "faxNumber": {"type": "string"}, + "phoneNumber": { + "type": "string", + "constraints": { + "matches": "^\\d{3}\\-\\d{4}\\ \\d{4}$" + } + }, + "url": {"type": "string"} + } + }, + "notes": {"type": "string"}, + "siteType": { + "type": "string", + "constraints": [ + {"enum": "site_type_enum"} + ] + }, + "firstName": {"type": "string"}, + "lastName": {"type": "string"}, + "description": {"type": "string"}, + "defaultSite": {"type": "boolean"}, + "active": {"type": "boolean"}, + "usages": { + "type": "array", + "items": { + "type": "object", + "fields": { + "requid": { + "type": "uid", + "constraints": { + "required": true + } + }, + "nonrequid": { + "type": "uid", + "constraints": { + "required": false + } + }, + "usage": { + "type": "string", + "constraints": [ + {"enum": "site_type_enum"} + ] + }, + "lastUsedOn": {"type": "date"} + } } - }, - "createdDate": {"type": "date"}, - "updatedDate": {"type": "date"}, - "uids": {"type": "array", "items":{"type":"uid"}}, - "uid": {"type": "uid"} - } - } + } + } + } + }, + "createdDate": {"type": "date"}, + "updatedDate": {"type": "date"}, + "uids": { + "type": "array", + "items": {"type": "uid"} + }, + "uid": {"type": "uid"} + } + } } From 5fee1137b3fdafa3bd83ff54d8d5de05c0e6f015 Mon Sep 17 00:00:00 2001 From: Luan Cestari Date: Fri, 9 Jan 2015 12:28:30 -0200 Subject: [PATCH 2/7] Fixes #19 Created couple of methods and refactored other to support and enahnce the Metadata and related classes. Also created tests to support these changes. --- .../crud/ConstraintValidatorTest.java | 5 + .../lightblue/metadata/AbstractMetadata.java | 35 ++- .../lightblue/metadata/EntitySchema.java | 10 +- .../lightblue/metadata/FieldConstraint.java | 6 + .../AbstractIntFieldConstraint.java | 6 + .../constraints/ArrayElementIdConstraint.java | 5 + .../metadata/constraints/EnumConstraint.java | 5 + .../constraints/IdentityConstraint.java | 7 +- .../constraints/MatchesConstraint.java | 5 + .../constraints/MinMaxConstraint.java | 5 + .../constraints/RequiredConstraint.java | 5 + .../test/metadata/FakeMetadataTest.java | 73 ++++++ test/src/test/resources/usermd.json | 222 ++++++++++++++++++ 13 files changed, 378 insertions(+), 11 deletions(-) create mode 100644 test/src/test/resources/usermd.json diff --git a/crud/src/test/java/com/redhat/lightblue/crud/ConstraintValidatorTest.java b/crud/src/test/java/com/redhat/lightblue/crud/ConstraintValidatorTest.java index 94cd7cfd..d4e2f93b 100644 --- a/crud/src/test/java/com/redhat/lightblue/crud/ConstraintValidatorTest.java +++ b/crud/src/test/java/com/redhat/lightblue/crud/ConstraintValidatorTest.java @@ -239,6 +239,11 @@ public boolean isValidForFieldType(Type fieldType) { return true; } + @Override + public String describeConstraint() { + return "test"; + } + } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java index 181062f6..f51fb7e3 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java @@ -142,38 +142,54 @@ public JsonNode getJSONSchema(String entityName, String version) { jsonNode.set("$schema", TextNode.valueOf("http://json-schema.org/draft-04/schema#")); jsonNode.set("type", TextNode.valueOf("object")); jsonNode.set("description", TextNode.valueOf(String.format("JSON schema for entity '%s' version '%s'", entityName, version))); - jsonNode.set("properties", properties); - buildJsonNodeSchema(properties, fieldTreeRoot); + if(fieldTreeRoot.hasChildren()) { + jsonNode.set("properties", properties); + buildJsonNodeSchema(properties, fieldTreeRoot); + properties.set("required", getRequiredFieldsArrayNode(entityMetadata)); + } + + return jsonNode; + } + + private ArrayNode getRequiredFieldsArrayNode(EntityMetadata entityMetadata) { ArrayNode value = new ArrayNode(JsonNodeFactory.instance); Field[] requiredFields = entityMetadata.getEntitySchema().getRequiredFields(); for (Field requiredField : requiredFields) { - TextNode.valueOf(requiredField.getFullPath().toString()); + value.add(TextNode.valueOf(requiredField.getFullPath().toString())); } - properties.set("required", value); - - return jsonNode; + return value; } private void buildJsonNodeSchema(ObjectNode jsonNode, FieldTreeNode fieldTreeRoot) { TreeMap fieldMap = new TreeMap<>(); Iterator children = fieldTreeRoot.getChildren(); Stack> fieldsPending = new Stack<>(); + Stack jsonParents = new Stack<>(); do{ FieldTreeNode fieldTreeChild = children.next(); if (fieldTreeChild instanceof ObjectField) { ObjectField of = (ObjectField) fieldTreeChild; ///// fieldsPending.push(children); + jsonParents.push(jsonNode); + ObjectNode child = new ObjectNode(JsonNodeFactory.instance); + jsonNode.set(of.getName(), child); + child.set("type", TextNode.valueOf(of.getType().getName())); + + + jsonNode = child; children = of.getChildren(); }else if (fieldTreeChild instanceof SimpleField) { SimpleField sf = (SimpleField) fieldTreeChild; ObjectNode json = new ObjectNode(JsonNodeFactory.instance); json.set("type", TextNode.valueOf(sf.getType().getName())); - //json.set("type", TextNode.valueOf(sf.getProperties().getName())); - + Object description = sf.getProperties().get("description"); + if(description != null) { + json.set("description", TextNode.valueOf(description.toString())); + } for (FieldConstraint fc : sf.getConstraints()) { - + json.set(fc.getType() , TextNode.valueOf(fc.describeConstraint())); } jsonNode.set(sf.getName(),json); } @@ -181,6 +197,7 @@ private void buildJsonNodeSchema(ObjectNode jsonNode, FieldTreeNode fieldTreeRoo if(!children.hasNext()){ if(!fieldsPending.empty()){ children = fieldsPending.pop(); + jsonNode = jsonParents.pop(); } else { break; } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/EntitySchema.java b/metadata/src/main/java/com/redhat/lightblue/metadata/EntitySchema.java index 51f9caee..f1595ca7 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/EntitySchema.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/EntitySchema.java @@ -308,7 +308,15 @@ public Type getType() { @Override public boolean hasChildren() { - return true; + if(fields == null) { + return false; + } else if(fields.getFields() == null){ + return false; + } else if(fields.getFields().hasNext()){ + return true; + } else { + return false; + } } @Override diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/FieldConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/FieldConstraint.java index e3a8aabe..fc2942a3 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/FieldConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/FieldConstraint.java @@ -31,4 +31,10 @@ public interface FieldConstraint { * Determines if the constraint is valid for the given field type */ boolean isValidForFieldType(Type fieldType); + + + /** + * Returns the description of the FieldConstraint (for documentation propose) + */ + String describeConstraint(); } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/AbstractIntFieldConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/AbstractIntFieldConstraint.java index 7852123e..00b2613a 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/AbstractIntFieldConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/AbstractIntFieldConstraint.java @@ -70,4 +70,10 @@ public int getValue() { public void setValue(int value) { this.value = value; } + + @Override + public String describeConstraint() { + return Integer.toString(value); + } + } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/ArrayElementIdConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/ArrayElementIdConstraint.java index 6bc42244..bcff485f 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/ArrayElementIdConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/ArrayElementIdConstraint.java @@ -41,4 +41,9 @@ public String getType() { public boolean isValidForFieldType(Type fieldType) { return !(fieldType instanceof ContainerType); } + + @Override + public String describeConstraint() { + return "Field is part of an array element id. All such fields of an array element is used to uniquely identify an array element"; + } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/EnumConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/EnumConstraint.java index b27be610..57ea0785 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/EnumConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/EnumConstraint.java @@ -48,6 +48,11 @@ public boolean isValidForFieldType(Type fieldType) { return StringType.TYPE.equals(fieldType); } + @Override + public String describeConstraint() { + return name; + } + public String getName() { return name; } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java index 6da6b903..4217c296 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java @@ -27,7 +27,7 @@ ; /** - * Field is part of identityconstraint + * Field is part of identity constraint */ public class IdentityConstraint implements FieldConstraint, Serializable { @@ -42,4 +42,9 @@ public String getType() { public boolean isValidForFieldType(Type fieldType) { return !(fieldType instanceof ContainerType); } + + @Override + public String describeConstraint() { + return "Field is part of identity constraint"; + } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java index ca6d2b16..8ef9dfc5 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java @@ -45,6 +45,11 @@ public boolean isValidForFieldType(Type fieldType) { return !(fieldType instanceof ContainerType); } + @Override + public String describeConstraint() { + return value.toString(); + } + public Pattern getValue() { return value; } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MinMaxConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MinMaxConstraint.java index 46582f5a..634fa53b 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MinMaxConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MinMaxConstraint.java @@ -57,6 +57,11 @@ public boolean isValidForFieldType(Type fieldType) { || BigIntegerType.TYPE.equals(fieldType); } + @Override + public String describeConstraint() { + return value.toString(); + } + public Number getValue() { return value; } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java index 9f8ebb8e..2a9e4074 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java @@ -42,6 +42,11 @@ public boolean isValidForFieldType(Type fieldType) { return true; } + @Override + public String describeConstraint() { + return value? "Field required constraint" : "Field not required constraint"; + } + public boolean getValue() { return value; } diff --git a/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java b/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java index 907fa089..36154050 100644 --- a/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java +++ b/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java @@ -6,11 +6,24 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.redhat.lightblue.metadata.DataStore; +import com.redhat.lightblue.metadata.parser.DataStoreParser; +import com.redhat.lightblue.metadata.parser.Extensions; +import com.redhat.lightblue.metadata.parser.JSONMetadataParser; +import com.redhat.lightblue.metadata.parser.MetadataParser; +import com.redhat.lightblue.metadata.types.DefaultTypes; +import com.redhat.lightblue.util.JsonUtils; +import org.json.JSONException; import org.junit.Test; import com.redhat.lightblue.Response; import com.redhat.lightblue.metadata.EntityInfo; import com.redhat.lightblue.metadata.EntityMetadata; +import org.skyscreamer.jsonassert.JSONAssert; + +import java.io.IOException; public class FakeMetadataTest { @@ -36,6 +49,66 @@ public void testEntityInfo_VersionDoesExist(){ assertTrue(metadata.checkVersionExists(entityName, version1)); } + @Test + public void testGetJSONSchemaNoFields(){ + String entityName = "fake"; + String version1 = "1.0.0"; + + FakeMetadata metadata = new FakeMetadata(); + + EntityInfo entityInfo = new EntityInfo(entityName); + metadata.setEntityInfo(entityInfo); + metadata.setEntityMetadata(entityName, version1, new EntityMetadata("fake EntityMetadata")); + + JsonNode jsonSchema = metadata.getJSONSchema(entityName, version1); + String actual = jsonSchema.toString(); + assertEquals("{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"type\":\"object\",\"description\":\"JSON schema for entity 'fake' version '1.0.0'\"}", actual); + } + + + @Test + public void testGetJSONSchemaWithFields() throws IOException, JSONException { + String entityName = "user"; + String version1 = "1.0.0"; + + FakeMetadata metadata = new FakeMetadata(); + + EntityInfo entityInfo = new EntityInfo(entityName); + metadata.setEntityInfo(entityInfo); + Extensions extensions = new Extensions<>(); + extensions.addDefaultExtensions(); + extensions.registerDataStoreParser("mongo", new DataStoreParser() { + @Override + public String getDefaultName() { + return "mongo"; + } + + @Override + public DataStore parse(String name, MetadataParser p, JsonNode node) { + return new DataStore() { + @Override + public String getBackend() { + return "mongo"; + } + }; + } + + @Override + public void convert(MetadataParser p, JsonNode emptyNode, DataStore object) { + } + }); + JSONMetadataParser parser = new JSONMetadataParser(extensions, new DefaultTypes(), JsonNodeFactory.withExactBigDecimals(false)); + JsonNode node = JsonUtils.json(getClass().getResourceAsStream("/usermd.json")); + EntityMetadata entityMetadata = parser.parseEntityMetadata(node); + metadata.setEntityMetadata(entityName, version1, entityMetadata); + + JsonNode jsonSchema = metadata.getJSONSchema(entityName, version1); + String actual = jsonSchema.toString(); + String expected = "{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"type\":\"object\",\"description\":\"JSON schema for entity 'user' version '1.0.0'\",\"properties\":{\"uid\":{\"type\":\"uid\"},\"nonrequid\":{\"type\":\"uid\",\"required\":\"Field not required constraint\"},\"iduid\":{\"type\":\"uid\",\"identity\":\"Field is part of identity constraint\"},\"personalInfo\":{\"type\":\"object\",\"lastName\":{\"type\":\"string\"},\"nonrequid\":{\"type\":\"uid\",\"required\":\"Field not required constraint\"},\"greeting\":{\"type\":\"string\"},\"department\":{\"type\":\"string\"},\"locale\":{\"type\":\"string\"},\"suffix\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"},\"timezone\":{\"type\":\"string\"},\"faxNumber\":{\"type\":\"string\"},\"phoneNumber\":{\"type\":\"string\",\"matches\":\"^\\\\d{3}\\\\-\\\\d{4}\\\\ \\\\d{4}$\"},\"email\":{\"type\":\"string\"},\"company\":{\"type\":\"string\"},\"firstName\":{\"type\":\"string\"},\"requid\":{\"type\":\"uid\",\"required\":\"Field required constraint\"},\"emailConfirmed\":{\"type\":\"boolean\"}},\"updatedDate\":{\"type\":\"date\"},\"password\":{\"type\":\"object\",\"hashed\":{\"type\":\"string\"},\"salt\":{\"type\":\"string\"}},\"contactPermissions\":{\"type\":\"object\",\"allowMailContact\":{\"type\":\"boolean\"},\"allowThirdPartyContact\":{\"type\":\"boolean\"},\"allowPhoneContact\":{\"type\":\"boolean\"},\"allowFaxContact\":{\"type\":\"boolean\"},\"allowEmailContact\":{\"type\":\"boolean\"}},\"_id\":{\"type\":\"string\",\"identity\":\"Field is part of identity constraint\"},\"active\":{\"type\":\"boolean\"},\"login\":{\"type\":\"string\",\"maxLength\":\"64\",\"minLength\":\"1\",\"required\":\"Field required constraint\"},\"objectType\":{\"type\":\"string\"},\"requid\":{\"type\":\"uid\",\"description\":\"test\",\"required\":\"Field required constraint\"},\"createdDate\":{\"type\":\"date\"},\"required\":[\"login\",\"personalInfo.requid\",\"requid\"]}}"; + JSONAssert.assertEquals(expected, actual, false); + } + + @Test(expected = IllegalStateException.class) public void testEntityInfo_DoesNotExist(){ String entityName = "fake"; diff --git a/test/src/test/resources/usermd.json b/test/src/test/resources/usermd.json new file mode 100644 index 00000000..53ab47ba --- /dev/null +++ b/test/src/test/resources/usermd.json @@ -0,0 +1,222 @@ +{ + "entityInfo": { + "name": "user", + "enums": [ + { + "name": "site_type_enum", + "values": [ + "billing", + "marketing", + "service", + "shipping" + ] + } + ], + "datastore": { + "backend": "mongo", + "datasource": "mongodata", + "collection": "user" + } + }, + "schema": { + "name": "user", + "version": { + "value": "5.0.0", + "changelog": "UID Test version" + }, + "status": { + "value": "active" + }, + "access": { + "insert": ["anyone"], + "find": ["anyone"], + "update": ["anyone"], + "delete": ["anyone"] + }, + "fields": { + "_id": { + "type": "string", + "constraints": { + "identity": 1 + } + }, + "objectType": {"type": "string"}, + "login": { + "type": "string", + "constraints": { + "maxLength": 64, + "minLength": 1, + "required": true + } + }, + "requid": { + "type": "uid", + "description": "test", + "constraints": { + "required": true + } + }, + "nonrequid": { + "type": "uid", + "constraints": { + "required": false + } + }, + "iduid": { + "type": "uid", + "constraints": { + "identity": true + } + }, + "password": { + "type": "object", + "fields": { + "hashed": {"type": "string"}, + "salt": {"type": "string"} + } + }, + "active": {"type": "boolean"}, + "contactPermissions": { + "type": "object", + "fields": { + "allowEmailContact": {"type": "boolean"}, + "allowFaxContact": {"type": "boolean"}, + "allowMailContact": {"type": "boolean"}, + "allowPhoneContact": {"type": "boolean"}, + "allowThirdPartyContact": {"type": "boolean"} + } + }, + "personalInfo": { + "type": "object", + "fields": { + "company": {"type": "string"}, + "greeting": {"type": "string"}, + "firstName": {"type": "string"}, + "lastName": {"type": "string"}, + "suffix": {"type": "string"}, + "title": {"type": "string"}, + "email": {"type": "string"}, + "emailConfirmed": {"type": "boolean"}, + "phoneNumber": { + "type": "string", + "constraints": { + "matches": "^\\d{3}\\-\\d{4}\\ \\d{4}$" + } + }, + "faxNumber": {"type": "string"}, + "locale": {"type": "string"}, + "timezone": {"type": "string"}, + "department": {"type": "string"}, + "requid": { + "type": "uid", + "constraints": { + "required": true + } + }, + "nonrequid": { + "type": "uid", + "constraints": { + "required": false + } + } + } + }, + "sites": { + "type": "array", + "items": { + "type": "object", + "fields": { + "siteId": {"type": "string"}, + "streetAddress": { + "type": "object", + "fields": { + "requid": { + "type": "uid", + "constraints": { + "required": true + } + }, + "nonrequid": { + "type": "uid", + "constraints": { + "required": false + } + }, + "address1": {"type": "string"}, + "address2": {"type": "string"}, + "address3": {"type": "string"}, + "address4": {"type": "string"}, + "city": {"type": "string"}, + "state": {"type": "string"}, + "country": {"type": "string"}, + "countryCode": {"type": "string"}, + "postalCode": {"type": "string"}, + "poBox": {"type": "boolean"} + } + }, + "contactInfo": { + "type": "object", + "fields": { + "emailAddress": {"type": "string"}, + "faxNumber": {"type": "string"}, + "phoneNumber": { + "type": "string", + "constraints": { + "matches": "^\\d{3}\\-\\d{4}\\ \\d{4}$" + } + }, + "url": {"type": "string"} + } + }, + "notes": {"type": "string"}, + "siteType": { + "type": "string", + "constraints": [ + {"enum": "site_type_enum"} + ] + }, + "firstName": {"type": "string"}, + "lastName": {"type": "string"}, + "description": {"type": "string"}, + "defaultSite": {"type": "boolean"}, + "active": {"type": "boolean"}, + "usages": { + "type": "array", + "items": { + "type": "object", + "fields": { + "requid": { + "type": "uid", + "constraints": { + "required": true + } + }, + "nonrequid": { + "type": "uid", + "constraints": { + "required": false + } + }, + "usage": { + "type": "string", + "constraints": [ + {"enum": "site_type_enum"} + ] + }, + "lastUsedOn": {"type": "date"} + } + } + } + } + } + }, + "createdDate": {"type": "date"}, + "updatedDate": {"type": "date"}, + "uids": { + "type": "array", + "items": {"type": "uid"} + }, + "uid": {"type": "uid"} + } + } +} From 1a184e64085aaf771ad0968c73e149c54496696c Mon Sep 17 00:00:00 2001 From: Luan Cestari Date: Fri, 9 Jan 2015 14:35:14 -0200 Subject: [PATCH 3/7] renamed to getDescription() --- .../java/com/redhat/lightblue/crud/ConstraintValidatorTest.java | 2 +- .../java/com/redhat/lightblue/metadata/AbstractMetadata.java | 2 +- .../java/com/redhat/lightblue/metadata/FieldConstraint.java | 2 +- .../metadata/constraints/AbstractIntFieldConstraint.java | 2 +- .../metadata/constraints/ArrayElementIdConstraint.java | 2 +- .../redhat/lightblue/metadata/constraints/EnumConstraint.java | 2 +- .../lightblue/metadata/constraints/IdentityConstraint.java | 2 +- .../lightblue/metadata/constraints/MatchesConstraint.java | 2 +- .../redhat/lightblue/metadata/constraints/MinMaxConstraint.java | 2 +- .../lightblue/metadata/constraints/RequiredConstraint.java | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crud/src/test/java/com/redhat/lightblue/crud/ConstraintValidatorTest.java b/crud/src/test/java/com/redhat/lightblue/crud/ConstraintValidatorTest.java index d4e2f93b..e19cb228 100644 --- a/crud/src/test/java/com/redhat/lightblue/crud/ConstraintValidatorTest.java +++ b/crud/src/test/java/com/redhat/lightblue/crud/ConstraintValidatorTest.java @@ -240,7 +240,7 @@ public boolean isValidForFieldType(Type fieldType) { } @Override - public String describeConstraint() { + public String getDescription() { return "test"; } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java index f51fb7e3..9f35eb0c 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java @@ -189,7 +189,7 @@ private void buildJsonNodeSchema(ObjectNode jsonNode, FieldTreeNode fieldTreeRoo } for (FieldConstraint fc : sf.getConstraints()) { - json.set(fc.getType() , TextNode.valueOf(fc.describeConstraint())); + json.set(fc.getType() , TextNode.valueOf(fc.getDescription())); } jsonNode.set(sf.getName(),json); } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/FieldConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/FieldConstraint.java index fc2942a3..d5fc6054 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/FieldConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/FieldConstraint.java @@ -36,5 +36,5 @@ public interface FieldConstraint { /** * Returns the description of the FieldConstraint (for documentation propose) */ - String describeConstraint(); + String getDescription(); } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/AbstractIntFieldConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/AbstractIntFieldConstraint.java index 00b2613a..3e320fda 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/AbstractIntFieldConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/AbstractIntFieldConstraint.java @@ -72,7 +72,7 @@ public void setValue(int value) { } @Override - public String describeConstraint() { + public String getDescription() { return Integer.toString(value); } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/ArrayElementIdConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/ArrayElementIdConstraint.java index bcff485f..01adc465 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/ArrayElementIdConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/ArrayElementIdConstraint.java @@ -43,7 +43,7 @@ public boolean isValidForFieldType(Type fieldType) { } @Override - public String describeConstraint() { + public String getDescription() { return "Field is part of an array element id. All such fields of an array element is used to uniquely identify an array element"; } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/EnumConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/EnumConstraint.java index 57ea0785..d17d0730 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/EnumConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/EnumConstraint.java @@ -49,7 +49,7 @@ public boolean isValidForFieldType(Type fieldType) { } @Override - public String describeConstraint() { + public String getDescription() { return name; } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java index 4217c296..acf05812 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java @@ -44,7 +44,7 @@ public boolean isValidForFieldType(Type fieldType) { } @Override - public String describeConstraint() { + public String getDescription() { return "Field is part of identity constraint"; } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java index 8ef9dfc5..0e2faa27 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java @@ -46,7 +46,7 @@ public boolean isValidForFieldType(Type fieldType) { } @Override - public String describeConstraint() { + public String getDescription() { return value.toString(); } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MinMaxConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MinMaxConstraint.java index 634fa53b..91c27371 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MinMaxConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MinMaxConstraint.java @@ -58,7 +58,7 @@ public boolean isValidForFieldType(Type fieldType) { } @Override - public String describeConstraint() { + public String getDescription() { return value.toString(); } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java index 2a9e4074..e27b7b19 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java @@ -43,7 +43,7 @@ public boolean isValidForFieldType(Type fieldType) { } @Override - public String describeConstraint() { + public String getDescription() { return value? "Field required constraint" : "Field not required constraint"; } From e78688f109d326c952ca0f82e6d4fdd0d8e19288 Mon Sep 17 00:00:00 2001 From: Luan Cestari Date: Wed, 21 Jan 2015 14:08:03 -0200 Subject: [PATCH 4/7] Updated PR Created a simple json-schema for validation and also moved most of the JSON content to files. --- .../test/metadata/FakeMetadataTest.java | 39 ++++++--- test/src/test/resources/metadata/schema.json | 35 ++++++++ .../testGetJSONSchemaNoFieldsExpected.json | 5 ++ .../testGetJSONSchemaWithFieldsExpected.json | 80 +++++++++++++++++++ 4 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 test/src/test/resources/metadata/schema.json create mode 100644 test/src/test/resources/testGetJSONSchemaNoFieldsExpected.json create mode 100644 test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json diff --git a/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java b/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java index 36154050..481a7881 100644 --- a/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java +++ b/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java @@ -8,6 +8,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.github.fge.jsonschema.core.exceptions.ProcessingException; +import com.github.fge.jsonschema.main.JsonSchema; import com.redhat.lightblue.metadata.DataStore; import com.redhat.lightblue.metadata.parser.DataStoreParser; import com.redhat.lightblue.metadata.parser.Extensions; @@ -15,7 +17,9 @@ import com.redhat.lightblue.metadata.parser.MetadataParser; import com.redhat.lightblue.metadata.types.DefaultTypes; import com.redhat.lightblue.util.JsonUtils; +import com.redhat.lightblue.util.test.FileUtil; import org.json.JSONException; +import org.junit.Assert; import org.junit.Test; import com.redhat.lightblue.Response; @@ -24,6 +28,7 @@ import org.skyscreamer.jsonassert.JSONAssert; import java.io.IOException; +import java.net.URISyntaxException; public class FakeMetadataTest { @@ -36,21 +41,17 @@ public void testEntityInfo_VersionDoesNotExist(){ public void testEntityInfo_VersionDoesExist(){ String entityName = "fake"; String version1 = "1.0.0"; - FakeMetadata metadata = new FakeMetadata(); - EntityInfo entityInfo = new EntityInfo(entityName); metadata.setEntityInfo(entityInfo); assertFalse(metadata.checkVersionExists(entityName, version1)); - metadata.setEntityMetadata(entityName, version1, new EntityMetadata("fake EntityMetadata")); - assertTrue(metadata.checkVersionExists(entityName, version1)); } @Test - public void testGetJSONSchemaNoFields(){ + public void testGetJSONSchemaNoFields() throws IOException, URISyntaxException, JSONException, ProcessingException { String entityName = "fake"; String version1 = "1.0.0"; @@ -62,17 +63,23 @@ public void testGetJSONSchemaNoFields(){ JsonNode jsonSchema = metadata.getJSONSchema(entityName, version1); String actual = jsonSchema.toString(); - assertEquals("{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"type\":\"object\",\"description\":\"JSON schema for entity 'fake' version '1.0.0'\"}", actual); + String expected = FileUtil.readFile("testGetJSONSchemaNoFieldsExpected.json"); + expected = expected.replace("descX","JSON schema for entity 'fake' version '1.0.0'"); + JsonSchema schema = JsonUtils.loadSchema("metadata/schema.json"); + String report = JsonUtils.jsonSchemaValidation(schema, jsonSchema); + if(report != null){ + Assert.fail("Expected validation to succeed! Resource: " + actual + " Messages: " + report.replaceAll("\n", " ")); + } + + JSONAssert.assertEquals(expected, actual, false); } @Test - public void testGetJSONSchemaWithFields() throws IOException, JSONException { + public void testGetJSONSchemaWithFields() throws IOException, JSONException, URISyntaxException, ProcessingException { String entityName = "user"; String version1 = "1.0.0"; - FakeMetadata metadata = new FakeMetadata(); - EntityInfo entityInfo = new EntityInfo(entityName); metadata.setEntityInfo(entityInfo); Extensions extensions = new Extensions<>(); @@ -104,7 +111,19 @@ public void convert(MetadataParser p, JsonNode emptyNode, DataStore ob JsonNode jsonSchema = metadata.getJSONSchema(entityName, version1); String actual = jsonSchema.toString(); - String expected = "{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"type\":\"object\",\"description\":\"JSON schema for entity 'user' version '1.0.0'\",\"properties\":{\"uid\":{\"type\":\"uid\"},\"nonrequid\":{\"type\":\"uid\",\"required\":\"Field not required constraint\"},\"iduid\":{\"type\":\"uid\",\"identity\":\"Field is part of identity constraint\"},\"personalInfo\":{\"type\":\"object\",\"lastName\":{\"type\":\"string\"},\"nonrequid\":{\"type\":\"uid\",\"required\":\"Field not required constraint\"},\"greeting\":{\"type\":\"string\"},\"department\":{\"type\":\"string\"},\"locale\":{\"type\":\"string\"},\"suffix\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"},\"timezone\":{\"type\":\"string\"},\"faxNumber\":{\"type\":\"string\"},\"phoneNumber\":{\"type\":\"string\",\"matches\":\"^\\\\d{3}\\\\-\\\\d{4}\\\\ \\\\d{4}$\"},\"email\":{\"type\":\"string\"},\"company\":{\"type\":\"string\"},\"firstName\":{\"type\":\"string\"},\"requid\":{\"type\":\"uid\",\"required\":\"Field required constraint\"},\"emailConfirmed\":{\"type\":\"boolean\"}},\"updatedDate\":{\"type\":\"date\"},\"password\":{\"type\":\"object\",\"hashed\":{\"type\":\"string\"},\"salt\":{\"type\":\"string\"}},\"contactPermissions\":{\"type\":\"object\",\"allowMailContact\":{\"type\":\"boolean\"},\"allowThirdPartyContact\":{\"type\":\"boolean\"},\"allowPhoneContact\":{\"type\":\"boolean\"},\"allowFaxContact\":{\"type\":\"boolean\"},\"allowEmailContact\":{\"type\":\"boolean\"}},\"_id\":{\"type\":\"string\",\"identity\":\"Field is part of identity constraint\"},\"active\":{\"type\":\"boolean\"},\"login\":{\"type\":\"string\",\"maxLength\":\"64\",\"minLength\":\"1\",\"required\":\"Field required constraint\"},\"objectType\":{\"type\":\"string\"},\"requid\":{\"type\":\"uid\",\"description\":\"test\",\"required\":\"Field required constraint\"},\"createdDate\":{\"type\":\"date\"},\"required\":[\"login\",\"personalInfo.requid\",\"requid\"]}}"; + String path = "testGetJSONSchemaWithFieldsExpected.json"; + String expected = FileUtil.readFile(path); + expected = expected.replace("descX", "JSON schema for entity 'user' version '1.0.0'"); + expected = expected.replace("notReqField", "Field not required constraint"); + expected = expected.replace("reqField", "Field required constraint"); + expected = expected.replace("matchPhone", "^\\\\d{3}\\\\-\\\\d{4}\\\\ \\\\d{4}$"); + expected = expected.replace("idenX", "Field is part of identity constraint"); + JsonSchema schema = JsonUtils.loadSchema("metadata/schema.json"); + String report = JsonUtils.jsonSchemaValidation(schema, jsonSchema); + if(report != null){ + Assert.fail("Expected validation to succeed! Resource: " + actual + " Messages: " + report.replaceAll("\n", " ")); + } + JSONAssert.assertEquals(expected, actual, false); } diff --git a/test/src/test/resources/metadata/schema.json b/test/src/test/resources/metadata/schema.json new file mode 100644 index 00000000..f1476c61 --- /dev/null +++ b/test/src/test/resources/metadata/schema.json @@ -0,0 +1,35 @@ +{ + "copyright": [ + "Copyright 2013 Red Hat, Inc. and/or its affiliates.", + "This file is part of lightblue.", + "This program is free software: you can redistribute it and/or modify", + "it under the terms of the GNU General Public License as published by", + "the Free Software Foundation, either version 3 of the License, or", + "(at your option) any later version.", + "This program is distributed in the hope that it will be useful,", + "but WITHOUT ANY WARRANTY; without even the implied warranty of", + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", + "GNU General Public License for more details.", + "You should have received a copy of the GNU General Public License", + "along with this program. If not, see ." + ], + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "$schema", + "type", + "description" + ], + "additionalProperties": true +} diff --git a/test/src/test/resources/testGetJSONSchemaNoFieldsExpected.json b/test/src/test/resources/testGetJSONSchemaNoFieldsExpected.json new file mode 100644 index 00000000..58f0c1de --- /dev/null +++ b/test/src/test/resources/testGetJSONSchemaNoFieldsExpected.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "descX" +} \ No newline at end of file diff --git a/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json b/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json new file mode 100644 index 00000000..100fd9e2 --- /dev/null +++ b/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "description": "descX", + "properties": { + "uid": {"type": "uid"}, + "nonrequid": { + "type": "uid", + "required": "notReqField" + }, + "iduid": { + "type": "uid", + "identity": "idenX" + }, + "personalInfo": { + "type": "object", + "lastName": {"type": "string"}, + "nonrequid": { + "type": "uid", + "required": "notReqField" + }, + "greeting": {"type": "string"}, + "department": {"type": "string"}, + "locale": {"type": "string"}, + "suffix": {"type": "string"}, + "title": {"type": "string"}, + "timezone": {"type": "string"}, + "faxNumber": {"type": "string"}, + "phoneNumber": { + "type": "string", + "matches": "matchPhone" + }, + "email": {"type": "string"}, + "company": {"type": "string"}, + "firstName": {"type": "string"}, + "requid": { + "type": "uid", + "required": "reqField" + }, + "emailConfirmed": {"type": "boolean"} + }, + "updatedDate": {"type": "date"}, + "password": { + "type": "object", + "hashed": {"type": "string"}, + "salt": {"type": "string"} + }, + "contactPermissions": { + "type": "object", + "allowMailContact": {"type": "boolean"}, + "allowThirdPartyContact": {"type": "boolean"}, + "allowPhoneContact": {"type": "boolean"}, + "allowFaxContact": {"type": "boolean"}, + "allowEmailContact": {"type": "boolean"} + }, + "_id": { + "type": "string", + "identity": "idenX" + }, + "active": {"type": "boolean"}, + "login": { + "type": "string", + "maxLength": "64", + "minLength": "1", + "required": "reqField" + }, + "objectType": {"type": "string"}, + "requid": { + "type": "uid", + "description": "test", + "required": "reqField" + }, + "createdDate": {"type": "date"}, + "required": [ + "login", + "personalInfo.requid", + "requid" + ] + } +} \ No newline at end of file From 9a6097aeebd0713be15de297b2fd4870e202b921 Mon Sep 17 00:00:00 2001 From: Luan Cestari Date: Thu, 29 Jan 2015 19:08:13 -0200 Subject: [PATCH 5/7] Fixed some changes required --- .../lightblue/config/CrudValidationTest.java | 8 +- .../config/MetadataConfigurationTest.java | 2 +- .../lightblue/metadata/AbstractMetadata.java | 104 ++++++++++++-- .../constraints/IdentityConstraint.java | 2 +- .../constraints/MatchesConstraint.java | 7 + .../constraints/RequiredConstraint.java | 2 +- .../test/metadata/FakeMetadataTest.java | 12 +- .../testGetJSONSchemaWithFieldsExpected.json | 127 +++++++++--------- test/src/test/resources/usermd.json | 7 +- .../redhat/lightblue/util/test/FileUtil.java | 15 ++- 10 files changed, 194 insertions(+), 92 deletions(-) diff --git a/config/src/test/java/com/redhat/lightblue/config/CrudValidationTest.java b/config/src/test/java/com/redhat/lightblue/config/CrudValidationTest.java index f25cd744..e9784c72 100644 --- a/config/src/test/java/com/redhat/lightblue/config/CrudValidationTest.java +++ b/config/src/test/java/com/redhat/lightblue/config/CrudValidationTest.java @@ -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); @@ -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); @@ -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); @@ -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); diff --git a/config/src/test/java/com/redhat/lightblue/config/MetadataConfigurationTest.java b/config/src/test/java/com/redhat/lightblue/config/MetadataConfigurationTest.java index 5d9c1e1e..a5df54c0 100644 --- a/config/src/test/java/com/redhat/lightblue/config/MetadataConfigurationTest.java +++ b/config/src/test/java/com/redhat/lightblue/config/MetadataConfigurationTest.java @@ -133,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 diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java index 9f35eb0c..88cb33a6 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java @@ -23,6 +23,10 @@ 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.*; @@ -135,17 +139,25 @@ public void setRoleMap(Map> roleMap) { @Override public JsonNode getJSONSchema(String entityName, String version) { ObjectNode jsonNode = new ObjectNode(JsonNodeFactory.instance); - ObjectNode properties = 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", properties); - buildJsonNodeSchema(properties, fieldTreeRoot); - properties.set("required", getRequiredFieldsArrayNode(entityMetadata)); + jsonNode.set("properties", propertiesNode); + buildJsonNodeSchema(propertiesNode, fieldTreeRoot); } return jsonNode; @@ -165,31 +177,53 @@ private void buildJsonNodeSchema(ObjectNode jsonNode, FieldTreeNode fieldTreeRoo Iterator children = fieldTreeRoot.getChildren(); Stack> fieldsPending = new Stack<>(); Stack jsonParents = new Stack<>(); + Stack requiredJsonParents = new Stack<>(); + ArrayNode requiredJsonNode = new ArrayNode(JsonNodeFactory.instance); do{ FieldTreeNode fieldTreeChild = children.next(); if (fieldTreeChild instanceof ObjectField) { - ObjectField of = (ObjectField) fieldTreeChild; - ///// 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); + } - jsonNode = child; + 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); - json.set("type", TextNode.valueOf(sf.getType().getName())); + 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()) { - json.set(fc.getType() , TextNode.valueOf(fc.getDescription())); + transformConstraintJsonNode(requiredJsonNode, sf.getName(), json, constraintsNode, fc); + } + if(constraintsNode.size() > 0 ) { + json.set("constraints", constraintsNode); } jsonNode.set(sf.getName(),json); } @@ -198,6 +232,17 @@ private void buildJsonNodeSchema(ObjectNode jsonNode, FieldTreeNode fieldTreeRoo 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; } @@ -205,6 +250,45 @@ private void buildJsonNodeSchema(ObjectNode jsonNode, FieldTreeNode fieldTreeRoo } 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 iterator; + Set 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())); + } } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java index acf05812..4b921e1b 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/IdentityConstraint.java @@ -45,6 +45,6 @@ public boolean isValidForFieldType(Type fieldType) { @Override public String getDescription() { - return "Field is part of identity constraint"; + return Boolean.TRUE.toString(); } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java index 0e2faa27..9727579a 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/MatchesConstraint.java @@ -57,4 +57,11 @@ public Pattern getValue() { public void setValue(Pattern v) { value = v; } + + public static void main(String[] args) { + FieldConstraint a = null; + MatchesConstraint c = (MatchesConstraint) a; + System.out.println("ok"); + + } } diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java index e27b7b19..a99d264f 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/constraints/RequiredConstraint.java @@ -44,7 +44,7 @@ public boolean isValidForFieldType(Type fieldType) { @Override public String getDescription() { - return value? "Field required constraint" : "Field not required constraint"; + return Boolean.toString(value); } public boolean getValue() { diff --git a/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java b/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java index 5f635cc6..da2f177c 100644 --- a/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java +++ b/test/src/test/java/com/redhat/lightblue/test/metadata/FakeMetadataTest.java @@ -81,7 +81,7 @@ public void testGetJSONSchemaNoFields() throws IOException, URISyntaxException, JsonNode jsonSchema = metadata.getJSONSchema(entityName, version1); String actual = jsonSchema.toString(); - String expected = FileUtil.readFile("testGetJSONSchemaNoFieldsExpected.json"); + String expected = FileUtil.readFileAndTrim("testGetJSONSchemaNoFieldsExpected.json"); expected = expected.replace("descX","JSON schema for entity 'fake' version '1.0.0'"); JsonSchema schema = JsonUtils.loadSchema("metadata/schema.json"); String report = JsonUtils.jsonSchemaValidation(schema, jsonSchema); @@ -131,17 +131,15 @@ public void convert(MetadataParser p, JsonNode emptyNode, DataStore ob String actual = jsonSchema.toString(); String path = "testGetJSONSchemaWithFieldsExpected.json"; String expected = FileUtil.readFile(path); - expected = expected.replace("descX", "JSON schema for entity 'user' version '1.0.0'"); - expected = expected.replace("notReqField", "Field not required constraint"); - expected = expected.replace("reqField", "Field required constraint"); - expected = expected.replace("matchPhone", "^\\\\d{3}\\\\-\\\\d{4}\\\\ \\\\d{4}$"); - expected = expected.replace("idenX", "Field is part of identity constraint"); JsonSchema schema = JsonUtils.loadSchema("metadata/schema.json"); String report = JsonUtils.jsonSchemaValidation(schema, jsonSchema); if(report != null){ Assert.fail("Expected validation to succeed! Resource: " + actual + " Messages: " + report.replaceAll("\n", " ")); } - + System.err.println("expected"); + System.err.println(expected.toString()); + System.err.println("actual"); + System.err.println(actual.toString()); JSONAssert.assertEquals(expected, actual, false); } diff --git a/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json b/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json index 100fd9e2..9de527a4 100644 --- a/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json +++ b/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json @@ -1,79 +1,74 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "description": "descX", - "properties": { - "uid": {"type": "uid"}, - "nonrequid": { - "type": "uid", - "required": "notReqField" + "$schema":"http://json-schema.org/draft-04/schema#", + "type":"object", + "description":"JSON schema for entity 'user' version '1.0.0'", + "properties":{ + "personalInfo":{ + "type":"object", + "properties":{ }, + "required":[ + "nonrequid", + "requid" + ] }, - "iduid": { - "type": "uid", - "identity": "idenX" + "iduid":{ + "type":"string" }, - "personalInfo": { - "type": "object", - "lastName": {"type": "string"}, - "nonrequid": { - "type": "uid", - "required": "notReqField" - }, - "greeting": {"type": "string"}, - "department": {"type": "string"}, - "locale": {"type": "string"}, - "suffix": {"type": "string"}, - "title": {"type": "string"}, - "timezone": {"type": "string"}, - "faxNumber": {"type": "string"}, - "phoneNumber": { - "type": "string", - "matches": "matchPhone" - }, - "email": {"type": "string"}, - "company": {"type": "string"}, - "firstName": {"type": "string"}, - "requid": { - "type": "uid", - "required": "reqField" - }, - "emailConfirmed": {"type": "boolean"} + "active":{ + "type":"boolean" }, - "updatedDate": {"type": "date"}, - "password": { - "type": "object", - "hashed": {"type": "string"}, - "salt": {"type": "string"} + "updatedDate":{ + "type":"date" }, - "contactPermissions": { - "type": "object", - "allowMailContact": {"type": "boolean"}, - "allowThirdPartyContact": {"type": "boolean"}, - "allowPhoneContact": {"type": "boolean"}, - "allowFaxContact": {"type": "boolean"}, - "allowEmailContact": {"type": "boolean"} + "login":{ + "type":"string", + "constraints":{ + "minLength":"1", + "maxLength":"64" + } }, - "_id": { - "type": "string", - "identity": "idenX" + "objectType":{ + "type":"string" }, - "active": {"type": "boolean"}, - "login": { - "type": "string", - "maxLength": "64", - "minLength": "1", - "required": "reqField" + "uid":{ + "type":"string" }, - "objectType": {"type": "string"}, - "requid": { - "type": "uid", - "description": "test", - "required": "reqField" + "password":{ + "type":"object", + "properties":{ + "salt":{ + "type":"string" + }, + "hashed":{ + "type":"string" + } + } }, - "createdDate": {"type": "date"}, - "required": [ + "createdDate":{ + "type":"date" + }, + "_id":{ + "type":"string" + }, + "nonrequid":{ + "type":"string" + }, + "requid":{ + "type":"string", + "description":"test" + }, + "contactPermissions":{ + "type":"object", + "properties":{ }, + "required":[ + "allowEmailContact" + ] + }, + "required":[ + "iduid", "login", - "personalInfo.requid", + "_id", + "nonrequid", "requid" ] } diff --git a/test/src/test/resources/usermd.json b/test/src/test/resources/usermd.json index 53ab47ba..18d9f2b9 100644 --- a/test/src/test/resources/usermd.json +++ b/test/src/test/resources/usermd.json @@ -79,7 +79,12 @@ "contactPermissions": { "type": "object", "fields": { - "allowEmailContact": {"type": "boolean"}, + "allowEmailContact": { + "type": "boolean", + "constraints": { + "required": false + } + }, "allowFaxContact": {"type": "boolean"}, "allowMailContact": {"type": "boolean"}, "allowPhoneContact": {"type": "boolean"}, diff --git a/util/src/main/java/com/redhat/lightblue/util/test/FileUtil.java b/util/src/main/java/com/redhat/lightblue/util/test/FileUtil.java index 3f247179..29f15eb6 100644 --- a/util/src/main/java/com/redhat/lightblue/util/test/FileUtil.java +++ b/util/src/main/java/com/redhat/lightblue/util/test/FileUtil.java @@ -33,7 +33,7 @@ private FileUtil() { } - public static String readFile(String path) throws IOException, URISyntaxException { + public static String readFileAndTrim(String path) throws IOException, URISyntaxException { StringBuilder everything = new StringBuilder(); try (InputStreamReader isr = new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream(path), Charset.forName("UTF-8")); @@ -45,4 +45,17 @@ public static String readFile(String path) throws IOException, URISyntaxExceptio } return everything.toString().replaceAll("\\s", "").replaceAll("\\r|\\n", ""); } + + public static String readFile(String path) throws IOException, URISyntaxException { + StringBuilder everything = new StringBuilder(); + + try (InputStreamReader isr = new InputStreamReader(Thread.currentThread().getContextClassLoader().getResourceAsStream(path), Charset.forName("UTF-8")); + BufferedReader bufferedReader = new BufferedReader(isr);) { + String line; + while ((line = bufferedReader.readLine()) != null) { + everything.append(line); + } + } + return everything.toString(); + } } From 64ff0323102bc7cb7c7d57ce9aeb58ad2976abd7 Mon Sep 17 00:00:00 2001 From: Luan Cestari Date: Wed, 11 Feb 2015 18:06:25 -0200 Subject: [PATCH 6/7] Merged & new enum test Merged with the latest changes. Also, As asked in the PR, update the test to cover the enum type usage. I included also the enum definition in the schema generated as it seems strange if you have the schema with external references --- .../lightblue/metadata/AbstractMetadata.java | 37 +++++++++++++++++++ .../testGetJSONSchemaWithFieldsExpected.json | 6 +++ test/src/test/resources/usermd.json | 6 +++ 3 files changed, 49 insertions(+) diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java index 88cb33a6..2925d708 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/AbstractMetadata.java @@ -159,6 +159,43 @@ public JsonNode getJSONSchema(String entityName, String version) { 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 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 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; } diff --git a/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json b/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json index 9de527a4..bd6b7201 100644 --- a/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json +++ b/test/src/test/resources/testGetJSONSchemaWithFieldsExpected.json @@ -3,6 +3,12 @@ "type":"object", "description":"JSON schema for entity 'user' version '1.0.0'", "properties":{ + "enumTest": { + "type":"string", + "constraints": { + "enum": "site_type_enum" + } + }, "personalInfo":{ "type":"object", "properties":{ }, diff --git a/test/src/test/resources/usermd.json b/test/src/test/resources/usermd.json index 18d9f2b9..0c257f18 100644 --- a/test/src/test/resources/usermd.json +++ b/test/src/test/resources/usermd.json @@ -34,6 +34,12 @@ "delete": ["anyone"] }, "fields": { + "enumTest": { + "type":"string", + "constraints": { + "enum": "site_type_enum" + } + }, "_id": { "type": "string", "constraints": { From 4e3f6ba7246126378b86415f31a3682937695625 Mon Sep 17 00:00:00 2001 From: Luan Cestari Date: Fri, 20 Feb 2015 13:46:25 -0200 Subject: [PATCH 7/7] Created a new role for json schema service --- config/src/test/resources/lightblue-metadata.json | 1 + .../main/java/com/redhat/lightblue/metadata/MetadataRoles.java | 1 + 2 files changed, 2 insertions(+) diff --git a/config/src/test/resources/lightblue-metadata.json b/config/src/test/resources/lightblue-metadata.json index 20f6d019..9acb67ea 100644 --- a/config/src/test/resources/lightblue-metadata.json +++ b/config/src/test/resources/lightblue-metadata.json @@ -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"], diff --git a/metadata/src/main/java/com/redhat/lightblue/metadata/MetadataRoles.java b/metadata/src/main/java/com/redhat/lightblue/metadata/MetadataRoles.java index 80631312..a5f96722 100644 --- a/metadata/src/main/java/com/redhat/lightblue/metadata/MetadataRoles.java +++ b/metadata/src/main/java/com/redhat/lightblue/metadata/MetadataRoles.java @@ -24,6 +24,7 @@ public enum MetadataRoles { FIND_ENTITY_NAMES("metadata.find.entityNames"), FIND_ENTITY_VERSIONS("metadata.find.entityVersions"), FIND_ENTITY_METADATA("metadata.find.entityMetadata"), + FIND_JSON_SCHEMA("metadata.find.jsonschema"), INSERT("metadata.insert"), INSERT_SCHEMA("metadata.insert.schema"), UPDATE_ENTITYINFO("metadata.update.entityInfo"),