Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Updated entity model specification, mapping, objects, and tests.
- Loading branch information
1 parent
0f44e20
commit 5b28a26
Showing
20 changed files
with
2,337 additions
and
1,140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package io.zentity.model; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import java.io.IOException; | ||
import java.util.*; | ||
|
||
public class Attribute { | ||
|
||
public static final Set<String> VALID_TYPES = new HashSet<>( | ||
Arrays.asList("string", "long", "integer", "short", "byte", "double", "float", "boolean") | ||
); | ||
|
||
private final String name; | ||
private String type = "string"; | ||
|
||
public Attribute(String name, JsonNode json) throws ValidationException { | ||
validateName(name); | ||
this.name = name; | ||
this.deserialize(json); | ||
} | ||
|
||
public Attribute(String name, String json) throws ValidationException, IOException { | ||
validateName(name); | ||
this.name = name; | ||
this.deserialize(json); | ||
} | ||
|
||
public String name() { | ||
return this.name; | ||
} | ||
|
||
public String type() { | ||
return this.type; | ||
} | ||
|
||
public void type(JsonNode value) throws ValidationException { | ||
validateType(value); | ||
this.type = value.textValue(); | ||
} | ||
|
||
private void validateName(String value) throws ValidationException { | ||
if (value.matches("^\\s*$")) | ||
throw new ValidationException("'attributes' has an attribute with empty name."); | ||
} | ||
|
||
private void validateType(JsonNode value) throws ValidationException { | ||
if (!value.isTextual()) | ||
throw new ValidationException("'attributes." + this.name + ".type' must be a string."); | ||
if (!VALID_TYPES.contains(value.textValue())) | ||
throw new ValidationException("'attributes." + this.name + ".type' does not support the value '" + value.textValue() + "'."); | ||
} | ||
|
||
private void validateObject(JsonNode object) throws ValidationException { | ||
if (!object.isObject()) | ||
throw new ValidationException("'attributes." + this.name + "' must be an object."); | ||
} | ||
|
||
/** | ||
* Deserialize, validate, and hold the state of an attribute object of an entity model. | ||
* Expected structure of the json variable: | ||
* <pre> | ||
* { | ||
* "type": ATTRIBUTE_TYPE | ||
* } | ||
* </pre> | ||
* | ||
* @param json Attribute object of an entity model. | ||
* @throws ValidationException | ||
*/ | ||
public void deserialize(JsonNode json) throws ValidationException { | ||
validateObject(json); | ||
|
||
// Validate and hold the state of fields. | ||
Iterator<Map.Entry<String, JsonNode>> fields = json.fields(); | ||
while (fields.hasNext()) { | ||
Map.Entry<String, JsonNode> field = fields.next(); | ||
String name = field.getKey(); | ||
JsonNode value = field.getValue(); | ||
switch (name) { | ||
case "type": | ||
this.type(value); | ||
break; | ||
default: | ||
throw new ValidationException("'attributes." + this.name + "." + name + "' is not a recognized field."); | ||
} | ||
} | ||
} | ||
|
||
public void deserialize(String json) throws ValidationException, IOException { | ||
deserialize(new ObjectMapper().readTree(json)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package io.zentity.model; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import java.io.IOException; | ||
import java.util.*; | ||
|
||
public class Index { | ||
|
||
public static final Set<String> REQUIRED_FIELDS = new HashSet<>( | ||
Arrays.asList("fields") | ||
); | ||
|
||
private final String name; | ||
private HashMap<String, IndexField> fields; | ||
private HashMap<String, HashMap<String, IndexField>> attributeIndexFieldsMap = new HashMap<>(); | ||
|
||
public Index(String name, JsonNode json) throws ValidationException { | ||
validateName(name); | ||
this.name = name; | ||
this.deserialize(json); | ||
} | ||
|
||
public Index(String name, String json) throws ValidationException, IOException { | ||
validateName(name); | ||
this.name = name; | ||
this.deserialize(json); | ||
} | ||
|
||
public String name() { | ||
return this.name; | ||
} | ||
|
||
public HashMap<String, HashMap<String, IndexField>> attributeIndexFieldsMap() { | ||
return this.attributeIndexFieldsMap; | ||
} | ||
|
||
public HashMap<String, IndexField> fields() { | ||
return this.fields; | ||
} | ||
|
||
public void fields(JsonNode value) throws ValidationException { | ||
validateFields(value); | ||
HashMap<String, IndexField> fields = new HashMap<>(); | ||
Iterator<Map.Entry<String, JsonNode>> children = value.fields(); | ||
while (children.hasNext()) { | ||
Map.Entry<String, JsonNode> child = children.next(); | ||
String fieldName = child.getKey(); | ||
JsonNode fieldObject = child.getValue(); | ||
validateField(fieldName, fieldObject); | ||
fields.put(fieldName, new IndexField(this.name, fieldName, fieldObject)); | ||
} | ||
this.fields = fields; | ||
this.rebuildAttributeIndexFieldsMap(); | ||
} | ||
|
||
private void validateName(String value) throws ValidationException { | ||
if (value.matches("^\\s*$")) | ||
throw new ValidationException("'indices' has an index with an empty name."); | ||
} | ||
|
||
private void validateField(String fieldName, JsonNode fieldObject) throws ValidationException { | ||
if (fieldName.equals("")) | ||
throw new ValidationException("'indices." + this.name + ".fields' has a field with an empty name."); | ||
if (!fieldObject.isObject()) | ||
throw new ValidationException("'indices." + this.name + ".fields." + fieldName + "' must be an object."); | ||
if (fieldObject.size() == 0) | ||
throw new ValidationException("'indices." + this.name + ".fields." + fieldName + "' must not be empty."); | ||
} | ||
|
||
private void validateFields(JsonNode value) throws ValidationException { | ||
if (!value.isObject()) | ||
throw new ValidationException("'indices." + this.name + ".fields' must be an object."); | ||
if (value.size() == 0) | ||
throw new ValidationException("'indices." + this.name + ".fields' must not be empty."); | ||
} | ||
|
||
private void validateObject(JsonNode object) throws ValidationException { | ||
if (!object.isObject()) | ||
throw new ValidationException("'indices." + this.name + "' must be an object."); | ||
if (object.size() == 0) | ||
throw new ValidationException("'indices." + this.name + "' is empty."); | ||
} | ||
|
||
/** | ||
* Create a reverse index of attribute names to index fields for faster lookup of index fields by attributes | ||
* during a resolution job. | ||
*/ | ||
private void rebuildAttributeIndexFieldsMap() { | ||
this.attributeIndexFieldsMap = new HashMap<>(); | ||
for (String indexFieldName : this.fields().keySet()) { | ||
String attributeName = this.fields().get(indexFieldName).attribute(); | ||
if (!this.attributeIndexFieldsMap.containsKey(attributeName)) | ||
this.attributeIndexFieldsMap.put(attributeName, new HashMap<>()); | ||
if (!this.attributeIndexFieldsMap.get(attributeName).containsKey(indexFieldName)) | ||
this.attributeIndexFieldsMap.get(attributeName).put(indexFieldName, this.fields.get(indexFieldName)); | ||
} | ||
} | ||
|
||
/** | ||
* Deserialize, validate, and hold the state of an index object of an entity model. | ||
* Expected structure of the json variable: | ||
* <pre> | ||
* { | ||
* "fields": { | ||
* INDEX_FIELD_NAME: INDEX_FIELD_OBJECT | ||
* ... | ||
* } | ||
* } | ||
* </pre> | ||
* | ||
* @param json Index object of an entity model. | ||
* @throws ValidationException | ||
*/ | ||
public void deserialize(JsonNode json) throws ValidationException { | ||
validateObject(json); | ||
|
||
// Validate the existence of required fields. | ||
for (String field : REQUIRED_FIELDS) | ||
if (!json.has(field)) | ||
throw new ValidationException("'indices." + this.name + "' is missing required field '" + field + "'."); | ||
|
||
// Validate and hold the state of fields. | ||
Iterator<Map.Entry<String, JsonNode>> fields = json.fields(); | ||
while (fields.hasNext()) { | ||
Map.Entry<String, JsonNode> field = fields.next(); | ||
String name = field.getKey(); | ||
JsonNode value = field.getValue(); | ||
switch (name) { | ||
case "fields": | ||
this.fields(value); | ||
break; | ||
default: | ||
throw new ValidationException("'indices." + this.name + "." + name + "' is not a recognized field."); | ||
} | ||
} | ||
} | ||
|
||
public void deserialize(String json) throws ValidationException, IOException { | ||
deserialize(new ObjectMapper().readTree(json)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package io.zentity.model; | ||
|
||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import java.io.IOException; | ||
import java.util.*; | ||
|
||
public class IndexField { | ||
|
||
public static final Set<String> REQUIRED_FIELDS = new HashSet<>( | ||
Arrays.asList("attribute") | ||
); | ||
|
||
private final String index; | ||
private final String name; | ||
private String attribute; | ||
private String matcher = null; | ||
|
||
public IndexField(String index, String name, JsonNode json) throws ValidationException { | ||
validateName(name); | ||
this.index = index; | ||
this.name = name; | ||
this.deserialize(json); | ||
} | ||
|
||
public IndexField(String index, String name, String json) throws ValidationException, IOException { | ||
validateName(name); | ||
this.index = index; | ||
this.name = name; | ||
this.deserialize(json); | ||
} | ||
|
||
public String index() { | ||
return this.index; | ||
} | ||
|
||
public String name() { | ||
return this.name; | ||
} | ||
|
||
public String attribute() { | ||
return this.attribute; | ||
} | ||
|
||
public void attribute(JsonNode value) throws ValidationException { | ||
validateAttribute(value); | ||
this.attribute = value.textValue(); | ||
} | ||
|
||
public String matcher() { | ||
return this.matcher; | ||
} | ||
|
||
public void matcher(JsonNode value) throws ValidationException { | ||
validateMatcher(value); | ||
this.matcher = value.isTextual() ? value.textValue() : null; | ||
} | ||
|
||
private void validateName(String value) throws ValidationException { | ||
if (value.matches("^\\s*$")) | ||
throw new ValidationException("'indices." + this.index + "' has a field with an empty name."); | ||
} | ||
|
||
private void validateAttribute(JsonNode value) throws ValidationException { | ||
if (!value.isTextual()) | ||
throw new ValidationException("'indices." + this.index + "." + this.name + ".attribute' must be a string."); | ||
if (value.textValue().matches("^\\s*$")) | ||
throw new ValidationException("'indices." + this.index + "." + this.name + ".attribute' must not be empty."); | ||
} | ||
|
||
private void validateMatcher(JsonNode value) throws ValidationException { | ||
if (!value.isTextual() && !value.isNull()) | ||
throw new ValidationException("'indices." + this.index + "." + this.name + ".matcher' must be a string or null."); | ||
if (value.textValue().matches("^\\s*$")) | ||
throw new ValidationException("'indices." + this.index + "." + this.name + ".matcher' must not be empty."); | ||
} | ||
|
||
private void validateObject(JsonNode object) throws ValidationException { | ||
if (!object.isObject()) | ||
throw new ValidationException("'indices." + this.index + "." + this.name + "' must be an object."); | ||
if (object.size() == 0) | ||
throw new ValidationException("'indices." + this.index + "." + this.name + "' is empty."); | ||
} | ||
|
||
/** | ||
* Deserialize, validate, and hold the state of a field object of an index field object of an entity model. | ||
* Expected structure of the json variable: | ||
* <pre> | ||
* { | ||
* "attribute": ATTRIBUTE_NAME, | ||
* "matcher": MATCHER_NAME | ||
* } | ||
* </pre> | ||
* | ||
* @param json Index object of an entity model. | ||
* @throws ValidationException | ||
*/ | ||
public void deserialize(JsonNode json) throws ValidationException { | ||
validateObject(json); | ||
|
||
// Validate the existence of required fields. | ||
for (String field : REQUIRED_FIELDS) | ||
if (!json.has(field)) | ||
throw new ValidationException("'indices." + this.index + "." + this.name + "' is missing required field '" + field + "'."); | ||
|
||
// Validate and hold the state of fields. | ||
Iterator<Map.Entry<String, JsonNode>> fields = json.fields(); | ||
while (fields.hasNext()) { | ||
Map.Entry<String, JsonNode> field = fields.next(); | ||
String name = field.getKey(); | ||
JsonNode value = field.getValue(); | ||
switch (name) { | ||
case "attribute": | ||
this.attribute(value); | ||
break; | ||
case "matcher": | ||
this.matcher(value); | ||
break; | ||
default: | ||
throw new ValidationException("'indices." + this.index + "." + this.name + "." + name + "' is not a recognized field."); | ||
} | ||
} | ||
} | ||
|
||
public void deserialize(String json) throws ValidationException, IOException { | ||
deserialize(new ObjectMapper().readTree(json)); | ||
} | ||
|
||
} |
Oops, something went wrong.