Skip to content

Commit b2bdb09

Browse files
committed
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.
1 parent df67a46 commit b2bdb09

33 files changed

Lines changed: 1843 additions & 1444 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.zentity.common;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.databind.SerializationFeature;
5+
6+
public class Json {
7+
8+
public static final ObjectMapper MAPPER = new ObjectMapper();
9+
public static final ObjectMapper ORDERED_MAPPER = new ObjectMapper().configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
10+
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.zentity.common;
2+
3+
import java.util.regex.Pattern;
4+
5+
public class Patterns {
6+
7+
public static final Pattern EMPTY_STRING = Pattern.compile("^\\s*$");
8+
public static final Pattern PERIOD = Pattern.compile("\\.");
9+
public static final Pattern VARIABLE = Pattern.compile("\\{\\{\\s*([^\\s{}]+)\\s*}}");
10+
public static final Pattern VARIABLE_PARAM = Pattern.compile("\\{\\{\\s*param\\.([^\\s{}]+)\\s*}}");
11+
12+
}
Lines changed: 65 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
package io.zentity.model;
22

3+
import com.fasterxml.jackson.core.JsonProcessingException;
34
import com.fasterxml.jackson.databind.JsonNode;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import io.zentity.common.Json;
6+
import io.zentity.common.Patterns;
57

68
import java.io.IOException;
7-
import java.util.*;
8-
import java.util.regex.Pattern;
9+
import java.util.Arrays;
10+
import java.util.Iterator;
11+
import java.util.Map;
12+
import java.util.Set;
13+
import java.util.TreeMap;
14+
import java.util.TreeSet;
915

1016
public class Attribute {
1117

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

1722
private final String name;
23+
private Map<String, String> params = new TreeMap<>();
1824
private String type = "string";
1925

20-
public Attribute(String name, JsonNode json) throws ValidationException {
26+
public Attribute(String name, JsonNode json) throws ValidationException, JsonProcessingException {
2127
validateName(name);
2228
this.name = name;
2329
this.deserialize(json);
@@ -29,89 +35,14 @@ public Attribute(String name, String json) throws ValidationException, IOExcepti
2935
this.deserialize(json);
3036
}
3137

32-
public static Boolean convertTypeBoolean(JsonNode value) {
33-
if (value.isNull())
34-
return null;
35-
return value.booleanValue();
36-
}
37-
38-
public static Number convertTypeNumber(JsonNode value) {
39-
if (value.isNull())
40-
return null;
41-
else if (value.isIntegralNumber())
42-
return value.bigIntegerValue();
43-
else if (value.isFloatingPointNumber())
44-
return value.doubleValue();
45-
else
46-
return value.numberValue();
47-
}
48-
49-
public static String convertTypeString(JsonNode value) {
50-
if (value.isNull())
51-
return null;
52-
return value.textValue();
53-
}
54-
55-
public static Object convertType(String targetType, JsonNode value) throws ValidationException {
56-
switch (targetType) {
57-
case "boolean":
58-
return convertTypeBoolean(value);
59-
case "number":
60-
return convertTypeNumber(value);
61-
case "string":
62-
return convertTypeString(value);
63-
default:
64-
throw new ValidationException("'" + targetType + " is not a recognized attribute data type.");
65-
}
66-
}
67-
68-
public static boolean isTypeBoolean(JsonNode value) {
69-
return value.isBoolean();
70-
}
71-
72-
public static boolean isTypeNumber(JsonNode value) {
73-
return value.isNumber();
74-
}
75-
76-
public static boolean isTypeString(JsonNode value) {
77-
return value.isTextual();
78-
}
79-
80-
public static void validateTypeBoolean(JsonNode value) throws ValidationException {
81-
if (!isTypeBoolean(value) && !value.isNull())
82-
throw new ValidationException("Expected 'boolean' attribute data type.");
83-
}
84-
85-
public static void validateTypeNumber(JsonNode value) throws ValidationException {
86-
if (!isTypeNumber(value) && !value.isNull())
87-
throw new ValidationException("Expected 'number' attribute data type.");
88-
}
89-
90-
public static void validateTypeString(JsonNode value) throws ValidationException {
91-
if (!isTypeString(value) && !value.isNull())
92-
throw new ValidationException("Expected 'string' attribute data type.");
93-
}
94-
95-
public static void validateType(String expectedType, JsonNode value) throws ValidationException {
96-
switch (expectedType) {
97-
case "boolean":
98-
validateTypeBoolean(value);
99-
break;
100-
case "number":
101-
validateTypeNumber(value);
102-
break;
103-
case "string":
104-
validateTypeString(value);
105-
break;
106-
default:
107-
throw new ValidationException("'" + expectedType + " is not a recognized attribute data type.");
108-
}
109-
}
110-
11138
public String name() {
11239
return this.name;
11340
}
11441

42+
public Map<String, String> params() {
43+
return this.params;
44+
}
45+
11546
public String type() {
11647
return this.type;
11748
}
@@ -122,19 +53,45 @@ public void type(JsonNode value) throws ValidationException {
12253
}
12354

12455
private void validateName(String value) throws ValidationException {
125-
if (REGEX_EMPTY.matcher(value).matches())
56+
if (Patterns.EMPTY_STRING.matcher(value).matches())
12657
throw new ValidationException("'attributes' has an attribute with empty name.");
12758
}
12859

60+
/**
61+
* Validate the value of "attributes".ATTRIBUTE_NAME."type".
62+
* Must be a non-empty string containing a recognized type.
63+
*
64+
* @param value The value of "attributes".ATTRIBUTE_NAME."type".
65+
* @throws ValidationException
66+
*/
12967
private void validateType(JsonNode value) throws ValidationException {
13068
if (!value.isTextual())
13169
throw new ValidationException("'attributes." + this.name + ".type' must be a string.");
132-
if (REGEX_EMPTY.matcher(value.textValue()).matches())
70+
if (Patterns.EMPTY_STRING.matcher(value.textValue()).matches())
13371
throw new ValidationException("'attributes." + this.name + ".type'' must not be empty.");
13472
if (!VALID_TYPES.contains(value.textValue()))
135-
throw new ValidationException("'attributes." + this.name + ".type' has an unrecognized data type '" + value.textValue() + "'.");
73+
throw new ValidationException("'attributes." + this.name + ".type' has an unrecognized type '" + value.textValue() + "'.");
74+
}
75+
76+
/**
77+
* Validate the value of "attributes".ATTRIBUTE_NAME."params".
78+
* Must be an object.
79+
*
80+
* @param value The value of "attributes".ATTRIBUTE_NAME."params".
81+
* @throws ValidationException
82+
*/
83+
private void validateParams(JsonNode value) throws ValidationException {
84+
if (!value.isObject())
85+
throw new ValidationException("'attributes." + this.name + ".params' must be an object.");
13686
}
13787

88+
/**
89+
* Validate the value of "attributes".ATTRIBUTE_NAME.
90+
* Must be an object.
91+
*
92+
* @param object The value of "attributes".ATTRIBUTE_NAME.
93+
* @throws ValidationException
94+
*/
13895
private void validateObject(JsonNode object) throws ValidationException {
13996
if (!object.isObject())
14097
throw new ValidationException("'attributes." + this.name + "' must be an object.");
@@ -151,8 +108,9 @@ private void validateObject(JsonNode object) throws ValidationException {
151108
*
152109
* @param json Attribute object of an entity model.
153110
* @throws ValidationException
111+
* @throws JsonProcessingException
154112
*/
155-
public void deserialize(JsonNode json) throws ValidationException {
113+
public void deserialize(JsonNode json) throws ValidationException, JsonProcessingException {
156114
validateObject(json);
157115

158116
// Validate and hold the state of fields.
@@ -165,14 +123,31 @@ public void deserialize(JsonNode json) throws ValidationException {
165123
case "type":
166124
this.type(value);
167125
break;
126+
case "params":
127+
// Set any params that were specified in the input, with the values serialized as strings.
128+
if (!value.isObject())
129+
throw new ValidationException("'attributes." + this.name + ".params' must be an object.");
130+
Iterator<Map.Entry<String, JsonNode>> paramsNode = value.fields();
131+
while (paramsNode.hasNext()) {
132+
Map.Entry<String, JsonNode> paramNode = paramsNode.next();
133+
String paramField = "params." + paramNode.getKey();
134+
JsonNode paramValue = paramNode.getValue();
135+
if (paramValue.isObject() || paramValue.isArray())
136+
this.params().put(paramField, Json.MAPPER.writeValueAsString(paramValue));
137+
else if (paramValue.isNull())
138+
this.params().put(paramField, "null");
139+
else
140+
this.params().put(paramField, paramValue.asText());
141+
}
142+
break;
168143
default:
169144
throw new ValidationException("'attributes." + this.name + "." + name + "' is not a recognized field.");
170145
}
171146
}
172147
}
173148

174149
public void deserialize(String json) throws ValidationException, IOException {
175-
deserialize(new ObjectMapper().readTree(json));
150+
deserialize(Json.MAPPER.readTree(json));
176151
}
177152

178153
}

‎src/main/java/io/zentity/model/Index.java‎

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
package io.zentity.model;
22

33
import com.fasterxml.jackson.databind.JsonNode;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import io.zentity.common.Json;
5+
import io.zentity.common.Patterns;
56

67
import java.io.IOException;
7-
import java.util.*;
8-
import java.util.regex.Pattern;
8+
import java.util.Arrays;
9+
import java.util.Iterator;
10+
import java.util.Map;
11+
import java.util.Set;
12+
import java.util.TreeMap;
13+
import java.util.TreeSet;
914

1015
public class Index {
1116

12-
public static final Set<String> REQUIRED_FIELDS = new HashSet<>(
17+
public static final Set<String> REQUIRED_FIELDS = new TreeSet<>(
1318
Arrays.asList("fields")
1419
);
15-
private static final Pattern REGEX_EMPTY = Pattern.compile("^\\s*$");
1620

1721
private final String name;
1822
private Map<String, IndexField> fields;
19-
private Map<String, Map<String, IndexField>> attributeIndexFieldsMap = new HashMap<>();
23+
private Map<String, Map<String, IndexField>> attributeIndexFieldsMap = new TreeMap<>();
2024

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

4549
public void fields(JsonNode value) throws ValidationException {
4650
validateFields(value);
47-
Map<String, IndexField> fields = new HashMap<>();
51+
Map<String, IndexField> fields = new TreeMap<>();
4852
Iterator<Map.Entry<String, JsonNode>> children = value.fields();
4953
while (children.hasNext()) {
5054
Map.Entry<String, JsonNode> child = children.next();
@@ -58,7 +62,7 @@ public void fields(JsonNode value) throws ValidationException {
5862
}
5963

6064
private void validateName(String value) throws ValidationException {
61-
if (REGEX_EMPTY.matcher(value).matches())
65+
if (Patterns.EMPTY_STRING.matcher(value).matches())
6266
throw new ValidationException("'indices' has an index with an empty name.");
6367
}
6468

@@ -90,11 +94,11 @@ private void validateObject(JsonNode object) throws ValidationException {
9094
* during a resolution job.
9195
*/
9296
private void rebuildAttributeIndexFieldsMap() {
93-
this.attributeIndexFieldsMap = new HashMap<>();
97+
this.attributeIndexFieldsMap = new TreeMap<>();
9498
for (String indexFieldName : this.fields().keySet()) {
9599
String attributeName = this.fields().get(indexFieldName).attribute();
96100
if (!this.attributeIndexFieldsMap.containsKey(attributeName))
97-
this.attributeIndexFieldsMap.put(attributeName, new HashMap<>());
101+
this.attributeIndexFieldsMap.put(attributeName, new TreeMap<>());
98102
if (!this.attributeIndexFieldsMap.get(attributeName).containsKey(indexFieldName))
99103
this.attributeIndexFieldsMap.get(attributeName).put(indexFieldName, this.fields.get(indexFieldName));
100104
}
@@ -140,7 +144,7 @@ public void deserialize(JsonNode json) throws ValidationException {
140144
}
141145

142146
public void deserialize(String json) throws ValidationException, IOException {
143-
deserialize(new ObjectMapper().readTree(json));
147+
deserialize(Json.MAPPER.readTree(json));
144148
}
145149

146150
}

‎src/main/java/io/zentity/model/IndexField.java‎

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
package io.zentity.model;
22

33
import com.fasterxml.jackson.databind.JsonNode;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import io.zentity.common.Json;
5+
import io.zentity.common.Patterns;
56

67
import java.io.IOException;
7-
import java.util.*;
8-
import java.util.regex.Pattern;
8+
import java.util.Arrays;
9+
import java.util.Iterator;
10+
import java.util.Map;
11+
import java.util.Set;
12+
import java.util.TreeSet;
913

1014
public class IndexField {
1115

12-
public static final Set<String> REQUIRED_FIELDS = new HashSet<>(
16+
public static final Set<String> REQUIRED_FIELDS = new TreeSet<>(
1317
Arrays.asList("attribute")
1418
);
15-
private static final Pattern REGEX_EMPTY = Pattern.compile("^\\s*$");
16-
private static final Pattern REGEX_PERIOD = Pattern.compile("\\.");
1719

1820
private final String index;
1921
private final String name;
@@ -73,28 +75,28 @@ public void matcher(JsonNode value) throws ValidationException {
7375
}
7476

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

8284
private void validateName(String value) throws ValidationException {
83-
if (REGEX_EMPTY.matcher(value).matches())
85+
if (Patterns.EMPTY_STRING.matcher(value).matches())
8486
throw new ValidationException("'indices." + this.index + "' has a field with an empty name.");
8587
}
8688

8789
private void validateAttribute(JsonNode value) throws ValidationException {
8890
if (!value.isTextual())
8991
throw new ValidationException("'indices." + this.index + "." + this.name + ".attribute' must be a string.");
90-
if (REGEX_EMPTY.matcher(value.textValue()).matches())
92+
if (Patterns.EMPTY_STRING.matcher(value.textValue()).matches())
9193
throw new ValidationException("'indices." + this.index + "." + this.name + ".attribute' must not be empty.");
9294
}
9395

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

@@ -146,7 +148,7 @@ public void deserialize(JsonNode json) throws ValidationException {
146148
}
147149

148150
public void deserialize(String json) throws ValidationException, IOException {
149-
deserialize(new ObjectMapper().readTree(json));
151+
deserialize(Json.MAPPER.readTree(json));
150152
}
151153

152154
}

0 commit comments

Comments
 (0)