Skip to content

Commit

Permalink
Able to pass arbitrary params from inputs to matchers. Deprecated the…
Browse files Browse the repository at this point in the history
… "matcher.type" field. Deprecated the "entity_id" field in the resolution input. Attribute values must always be wrapped in an array. Using TreeMap and TreeSet instead of HashMap and HashSet for deterministic behavior. New packages: io.zentity.resolution.input and io.zentity.common.
  • Loading branch information
davemoore- committed May 1, 2018
1 parent df67a46 commit b2bdb09
Show file tree
Hide file tree
Showing 33 changed files with 1,843 additions and 1,444 deletions.
11 changes: 11 additions & 0 deletions src/main/java/io/zentity/common/Json.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.zentity.common;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class Json {

public static final ObjectMapper MAPPER = new ObjectMapper();
public static final ObjectMapper ORDERED_MAPPER = new ObjectMapper().configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);

}
12 changes: 12 additions & 0 deletions src/main/java/io/zentity/common/Patterns.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.zentity.common;

import java.util.regex.Pattern;

public class Patterns {

public static final Pattern EMPTY_STRING = Pattern.compile("^\\s*$");
public static final Pattern PERIOD = Pattern.compile("\\.");
public static final Pattern VARIABLE = Pattern.compile("\\{\\{\\s*([^\\s{}]+)\\s*}}");
public static final Pattern VARIABLE_PARAM = Pattern.compile("\\{\\{\\s*param\\.([^\\s{}]+)\\s*}}");

}
155 changes: 65 additions & 90 deletions src/main/java/io/zentity/model/Attribute.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
package io.zentity.model;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.zentity.common.Json;
import io.zentity.common.Patterns;

import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class Attribute {

public static final Set<String> VALID_TYPES = new HashSet<>(
public static final Set<String> VALID_TYPES = new TreeSet<>(
Arrays.asList("string", "number", "boolean")
);
private static final Pattern REGEX_EMPTY = Pattern.compile("^\\s*$");

private final String name;
private Map<String, String> params = new TreeMap<>();
private String type = "string";

public Attribute(String name, JsonNode json) throws ValidationException {
public Attribute(String name, JsonNode json) throws ValidationException, JsonProcessingException {
validateName(name);
this.name = name;
this.deserialize(json);
Expand All @@ -29,89 +35,14 @@ public Attribute(String name, String json) throws ValidationException, IOExcepti
this.deserialize(json);
}

public static Boolean convertTypeBoolean(JsonNode value) {
if (value.isNull())
return null;
return value.booleanValue();
}

public static Number convertTypeNumber(JsonNode value) {
if (value.isNull())
return null;
else if (value.isIntegralNumber())
return value.bigIntegerValue();
else if (value.isFloatingPointNumber())
return value.doubleValue();
else
return value.numberValue();
}

public static String convertTypeString(JsonNode value) {
if (value.isNull())
return null;
return value.textValue();
}

public static Object convertType(String targetType, JsonNode value) throws ValidationException {
switch (targetType) {
case "boolean":
return convertTypeBoolean(value);
case "number":
return convertTypeNumber(value);
case "string":
return convertTypeString(value);
default:
throw new ValidationException("'" + targetType + " is not a recognized attribute data type.");
}
}

public static boolean isTypeBoolean(JsonNode value) {
return value.isBoolean();
}

public static boolean isTypeNumber(JsonNode value) {
return value.isNumber();
}

public static boolean isTypeString(JsonNode value) {
return value.isTextual();
}

public static void validateTypeBoolean(JsonNode value) throws ValidationException {
if (!isTypeBoolean(value) && !value.isNull())
throw new ValidationException("Expected 'boolean' attribute data type.");
}

public static void validateTypeNumber(JsonNode value) throws ValidationException {
if (!isTypeNumber(value) && !value.isNull())
throw new ValidationException("Expected 'number' attribute data type.");
}

public static void validateTypeString(JsonNode value) throws ValidationException {
if (!isTypeString(value) && !value.isNull())
throw new ValidationException("Expected 'string' attribute data type.");
}

public static void validateType(String expectedType, JsonNode value) throws ValidationException {
switch (expectedType) {
case "boolean":
validateTypeBoolean(value);
break;
case "number":
validateTypeNumber(value);
break;
case "string":
validateTypeString(value);
break;
default:
throw new ValidationException("'" + expectedType + " is not a recognized attribute data type.");
}
}

public String name() {
return this.name;
}

public Map<String, String> params() {
return this.params;
}

public String type() {
return this.type;
}
Expand All @@ -122,19 +53,45 @@ public void type(JsonNode value) throws ValidationException {
}

private void validateName(String value) throws ValidationException {
if (REGEX_EMPTY.matcher(value).matches())
if (Patterns.EMPTY_STRING.matcher(value).matches())
throw new ValidationException("'attributes' has an attribute with empty name.");
}

/**
* Validate the value of "attributes".ATTRIBUTE_NAME."type".
* Must be a non-empty string containing a recognized type.
*
* @param value The value of "attributes".ATTRIBUTE_NAME."type".
* @throws ValidationException
*/
private void validateType(JsonNode value) throws ValidationException {
if (!value.isTextual())
throw new ValidationException("'attributes." + this.name + ".type' must be a string.");
if (REGEX_EMPTY.matcher(value.textValue()).matches())
if (Patterns.EMPTY_STRING.matcher(value.textValue()).matches())
throw new ValidationException("'attributes." + this.name + ".type'' must not be empty.");
if (!VALID_TYPES.contains(value.textValue()))
throw new ValidationException("'attributes." + this.name + ".type' has an unrecognized data type '" + value.textValue() + "'.");
throw new ValidationException("'attributes." + this.name + ".type' has an unrecognized type '" + value.textValue() + "'.");
}

/**
* Validate the value of "attributes".ATTRIBUTE_NAME."params".
* Must be an object.
*
* @param value The value of "attributes".ATTRIBUTE_NAME."params".
* @throws ValidationException
*/
private void validateParams(JsonNode value) throws ValidationException {
if (!value.isObject())
throw new ValidationException("'attributes." + this.name + ".params' must be an object.");
}

/**
* Validate the value of "attributes".ATTRIBUTE_NAME.
* Must be an object.
*
* @param object The value of "attributes".ATTRIBUTE_NAME.
* @throws ValidationException
*/
private void validateObject(JsonNode object) throws ValidationException {
if (!object.isObject())
throw new ValidationException("'attributes." + this.name + "' must be an object.");
Expand All @@ -151,8 +108,9 @@ private void validateObject(JsonNode object) throws ValidationException {
*
* @param json Attribute object of an entity model.
* @throws ValidationException
* @throws JsonProcessingException
*/
public void deserialize(JsonNode json) throws ValidationException {
public void deserialize(JsonNode json) throws ValidationException, JsonProcessingException {
validateObject(json);

// Validate and hold the state of fields.
Expand All @@ -165,14 +123,31 @@ public void deserialize(JsonNode json) throws ValidationException {
case "type":
this.type(value);
break;
case "params":
// Set any params that were specified in the input, with the values serialized as strings.
if (!value.isObject())
throw new ValidationException("'attributes." + this.name + ".params' must be an object.");
Iterator<Map.Entry<String, JsonNode>> paramsNode = value.fields();
while (paramsNode.hasNext()) {
Map.Entry<String, JsonNode> paramNode = paramsNode.next();
String paramField = "params." + paramNode.getKey();
JsonNode paramValue = paramNode.getValue();
if (paramValue.isObject() || paramValue.isArray())
this.params().put(paramField, Json.MAPPER.writeValueAsString(paramValue));
else if (paramValue.isNull())
this.params().put(paramField, "null");
else
this.params().put(paramField, paramValue.asText());
}
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));
deserialize(Json.MAPPER.readTree(json));
}

}
26 changes: 15 additions & 11 deletions src/main/java/io/zentity/model/Index.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package io.zentity.model;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.zentity.common.Json;
import io.zentity.common.Patterns;

import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class Index {

public static final Set<String> REQUIRED_FIELDS = new HashSet<>(
public static final Set<String> REQUIRED_FIELDS = new TreeSet<>(
Arrays.asList("fields")
);
private static final Pattern REGEX_EMPTY = Pattern.compile("^\\s*$");

private final String name;
private Map<String, IndexField> fields;
private Map<String, Map<String, IndexField>> attributeIndexFieldsMap = new HashMap<>();
private Map<String, Map<String, IndexField>> attributeIndexFieldsMap = new TreeMap<>();

public Index(String name, JsonNode json) throws ValidationException {
validateName(name);
Expand Down Expand Up @@ -44,7 +48,7 @@ public Map<String, IndexField> fields() {

public void fields(JsonNode value) throws ValidationException {
validateFields(value);
Map<String, IndexField> fields = new HashMap<>();
Map<String, IndexField> fields = new TreeMap<>();
Iterator<Map.Entry<String, JsonNode>> children = value.fields();
while (children.hasNext()) {
Map.Entry<String, JsonNode> child = children.next();
Expand All @@ -58,7 +62,7 @@ public void fields(JsonNode value) throws ValidationException {
}

private void validateName(String value) throws ValidationException {
if (REGEX_EMPTY.matcher(value).matches())
if (Patterns.EMPTY_STRING.matcher(value).matches())
throw new ValidationException("'indices' has an index with an empty name.");
}

Expand Down Expand Up @@ -90,11 +94,11 @@ private void validateObject(JsonNode object) throws ValidationException {
* during a resolution job.
*/
private void rebuildAttributeIndexFieldsMap() {
this.attributeIndexFieldsMap = new HashMap<>();
this.attributeIndexFieldsMap = new TreeMap<>();
for (String indexFieldName : this.fields().keySet()) {
String attributeName = this.fields().get(indexFieldName).attribute();
if (!this.attributeIndexFieldsMap.containsKey(attributeName))
this.attributeIndexFieldsMap.put(attributeName, new HashMap<>());
this.attributeIndexFieldsMap.put(attributeName, new TreeMap<>());
if (!this.attributeIndexFieldsMap.get(attributeName).containsKey(indexFieldName))
this.attributeIndexFieldsMap.get(attributeName).put(indexFieldName, this.fields.get(indexFieldName));
}
Expand Down Expand Up @@ -140,7 +144,7 @@ public void deserialize(JsonNode json) throws ValidationException {
}

public void deserialize(String json) throws ValidationException, IOException {
deserialize(new ObjectMapper().readTree(json));
deserialize(Json.MAPPER.readTree(json));
}

}
24 changes: 13 additions & 11 deletions src/main/java/io/zentity/model/IndexField.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package io.zentity.model;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.zentity.common.Json;
import io.zentity.common.Patterns;

import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

public class IndexField {

public static final Set<String> REQUIRED_FIELDS = new HashSet<>(
public static final Set<String> REQUIRED_FIELDS = new TreeSet<>(
Arrays.asList("attribute")
);
private static final Pattern REGEX_EMPTY = Pattern.compile("^\\s*$");
private static final Pattern REGEX_PERIOD = Pattern.compile("\\.");

private final String index;
private final String name;
Expand Down Expand Up @@ -73,28 +75,28 @@ public void matcher(JsonNode value) throws ValidationException {
}

private void nameToPaths(String name) {
String[] parts = REGEX_PERIOD.split(name);
String[] parts = Patterns.PERIOD.split(name);
this.path = "/" + String.join("/", parts);
if (parts.length > 1)
this.pathParent = "/" + String.join("/", Arrays.copyOf(parts, parts.length - 1));
}

private void validateName(String value) throws ValidationException {
if (REGEX_EMPTY.matcher(value).matches())
if (Patterns.EMPTY_STRING.matcher(value).matches())
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 (REGEX_EMPTY.matcher(value.textValue()).matches())
if (Patterns.EMPTY_STRING.matcher(value.textValue()).matches())
throw new ValidationException("'indices." + this.index + "." + this.name + ".attribute' must not be empty.");
}

private void validateMatcher(JsonNode value) throws ValidationException {
if (!value.isTextual())
throw new ValidationException("'indices." + this.index + "." + this.name + ".matcher' must be a string.");
if (REGEX_EMPTY.matcher(value.textValue()).matches())
if (Patterns.EMPTY_STRING.matcher(value.textValue()).matches())
throw new ValidationException("'indices." + this.index + "." + this.name + ".matcher' must not be empty.");
}

Expand Down Expand Up @@ -146,7 +148,7 @@ public void deserialize(JsonNode json) throws ValidationException {
}

public void deserialize(String json) throws ValidationException, IOException {
deserialize(new ObjectMapper().readTree(json));
deserialize(Json.MAPPER.readTree(json));
}

}
Loading

0 comments on commit b2bdb09

Please sign in to comment.