Permalink
Browse files

Able to pass arbitrary params from inputs to matchers. Deprecated the…

… "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 Apr 28, 2018
1 parent df67a46 commit b2bdb091428df5fa736883758ce2c289d68730b8
Showing with 1,843 additions and 1,444 deletions.
  1. +11 −0 src/main/java/io/zentity/common/Json.java
  2. +12 −0 src/main/java/io/zentity/common/Patterns.java
  3. +65 −90 src/main/java/io/zentity/model/Attribute.java
  4. +15 −11 src/main/java/io/zentity/model/Index.java
  5. +13 −11 src/main/java/io/zentity/model/IndexField.java
  6. +65 −32 src/main/java/io/zentity/model/Matcher.java
  7. +7 −7 src/main/java/io/zentity/model/Model.java
  8. +13 −10 src/main/java/io/zentity/model/Resolver.java
  9. +105 −100 src/main/java/io/zentity/resolution/Job.java
  10. +184 −0 src/main/java/io/zentity/resolution/input/Attribute.java
  11. +278 −0 src/main/java/io/zentity/resolution/input/Input.java
  12. +43 −0 src/main/java/io/zentity/resolution/input/scope/Exclude.java
  13. +43 −0 src/main/java/io/zentity/resolution/input/scope/Include.java
  14. +55 −0 src/main/java/io/zentity/resolution/input/scope/Scope.java
  15. +154 −0 src/main/java/io/zentity/resolution/input/scope/ScopeField.java
  16. +36 −0 src/main/java/io/zentity/resolution/input/value/BooleanValue.java
  17. +40 −0 src/main/java/io/zentity/resolution/input/value/NumberValue.java
  18. +35 −0 src/main/java/io/zentity/resolution/input/value/StringValue.java
  19. +68 −0 src/main/java/io/zentity/resolution/input/value/Value.java
  20. +34 −0 src/main/java/io/zentity/resolution/input/value/ValueInterface.java
  21. +5 −1 src/main/java/org/elasticsearch/plugin/zentity/HomeAction.java
  22. +13 −6 src/main/java/org/elasticsearch/plugin/zentity/ModelsAction.java
  23. +25 −556 src/main/java/org/elasticsearch/plugin/zentity/ResolutionAction.java
  24. +1 −7 src/main/java/org/elasticsearch/plugin/zentity/ZentityPlugin.java
  25. +59 −55 src/test/java/io/zentity/model/AttributeTest.java
  26. +1 −44 src/test/java/io/zentity/model/MatcherTest.java
  27. +1 −1 src/test/java/io/zentity/model/ModelTest.java
  28. +2 −3 src/test/java/io/zentity/resolution/AbstractITCase.java
  29. +53 −49 src/test/java/io/zentity/resolution/JobIT.java
  30. +21 −28 src/test/java/io/zentity/resolution/JobTest.java
  31. +383 −430 src/test/java/org/elasticsearch/plugin/zentity/ResolutionActionTest.java
  32. +2 −1 src/test/java/org/elasticsearch/plugin/zentity/ZentityPluginIT.java
  33. +1 −2 src/test/resources/TestEntityModel.json
@@ -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);
}
@@ -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*}}");
}
@@ -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);
@@ -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;
}
@@ -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.");
@@ -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.
@@ -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));
}
}
@@ -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);
@@ -44,7 +48,7 @@ public String name() {
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();
@@ -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.");
}
@@ -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));
}
@@ -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));
}
}
@@ -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;
@@ -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.");
}
@@ -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));
}
}
Oops, something went wrong.

0 comments on commit b2bdb09

Please sign in to comment.