Skip to content

Commit

Permalink
Updated entity model specification, mapping, objects, and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
davemoore- committed Apr 7, 2018
1 parent 0f44e20 commit 5b28a26
Show file tree
Hide file tree
Showing 20 changed files with 2,337 additions and 1,140 deletions.
95 changes: 95 additions & 0 deletions src/main/java/io/zentity/model/Attribute.java
@@ -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));
}

}
144 changes: 144 additions & 0 deletions src/main/java/io/zentity/model/Index.java
@@ -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));
}

}
130 changes: 130 additions & 0 deletions src/main/java/io/zentity/model/IndexField.java
@@ -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));
}

}

0 comments on commit 5b28a26

Please sign in to comment.