Skip to content

Commit a57958d

Browse files
committed
Introduced the concept of resolver priority. When a resolution job uses resolvers with different priority levels, then the higher priority resolvers either must match the document or must not exist in the document.
1 parent 6ad47c4 commit a57958d

File tree

4 files changed

+94
-8
lines changed

4 files changed

+94
-8
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Once you have installed Elasticsearch, you can install zentity from a remote URL
3131

3232
Example:
3333

34-
`elasticsearch-plugin install https://zentity.io/releases/zentity-1.0.3-elasticsearch-7.2.0.zip`
34+
`elasticsearch-plugin install https://zentity.io/releases/zentity-1.1.0-elasticsearch-7.2.0.zip`
3535

3636
Read the [installation](https://zentity.io/docs/installation) docs for more details.
3737

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<zentity.author>Dave Moore</zentity.author>
1515
<zentity.classname>org.elasticsearch.plugin.zentity.ZentityPlugin</zentity.classname>
1616
<zentity.website>https://zentity.io</zentity.website>
17-
<zentity.version>1.0.3</zentity.version>
17+
<zentity.version>1.1.0</zentity.version>
1818
<!-- dependency versions -->
1919
<elasticsearch.version>7.2.0</elasticsearch.version>
2020
<jackson.version>2.9.9</jackson.version>

src/main/java/io/zentity/model/Resolver.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class Resolver {
1919

2020
private final String name;
2121
private Set<String> attributes = new TreeSet<>();
22+
private int priority = 0;
2223

2324
public Resolver(String name, JsonNode json) throws ValidationException {
2425
validateName(name);
@@ -40,6 +41,8 @@ public Set<String> attributes() {
4041
return this.attributes;
4142
}
4243

44+
public int priority () { return this.priority; }
45+
4346
public void attributes(JsonNode value) throws ValidationException {
4447
validateAttributes(value);
4548
Set<String> attributes = new TreeSet<>();
@@ -48,6 +51,11 @@ public void attributes(JsonNode value) throws ValidationException {
4851
this.attributes = attributes;
4952
}
5053

54+
public void priority(JsonNode value) throws ValidationException {
55+
validatePriority(value);
56+
this.priority = value.asInt();
57+
}
58+
5159
private void validateName(String value) throws ValidationException {
5260
if (Patterns.EMPTY_STRING.matcher(value).matches())
5361
throw new ValidationException("'resolvers' has a resolver with an empty name.");
@@ -67,6 +75,11 @@ private void validateAttributes(JsonNode value) throws ValidationException {
6775
}
6876
}
6977

78+
private void validatePriority(JsonNode value) throws ValidationException {
79+
if (!value.isInt())
80+
throw new ValidationException("'resolvers." + this.name + ".priority' must be an integer.");
81+
}
82+
7083
private void validateObject(JsonNode object) throws ValidationException {
7184
if (!object.isObject())
7285
throw new ValidationException("'resolvers." + this.name + "' must be an object.");
@@ -82,7 +95,8 @@ private void validateObject(JsonNode object) throws ValidationException {
8295
* "attributes": [
8396
* ATTRIBUTE_NAME,
8497
* ...
85-
* ]
98+
* ],
99+
* "priority": INTEGER
86100
* }
87101
* </pre>
88102
*
@@ -107,6 +121,9 @@ public void deserialize(JsonNode json) throws ValidationException {
107121
case "attributes":
108122
this.attributes(value);
109123
break;
124+
case "priority":
125+
this.priority(value);
126+
break;
110127
default:
111128
throw new ValidationException("'resolvers." + this.name + "." + name + "' is not a recognized field.");
112129
}

src/main/java/io/zentity/resolution/Job.java

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import java.io.IOException;
2828
import java.util.ArrayList;
29+
import java.util.Arrays;
2930
import java.util.Collections;
3031
import java.util.List;
3132
import java.util.Map;
@@ -420,6 +421,24 @@ public static Map<String, Integer> countAttributesAcrossResolvers(Model model, L
420421
return counts;
421422
}
422423

424+
/**
425+
* Group resolvers by their level of priority.
426+
*
427+
* @param model The entity model.
428+
* @param resolvers The names of the resolvers to reference in the entity model.
429+
* @return For each priority level, the names of the resolvers in that priority level.
430+
*/
431+
public static TreeMap<Integer, List<String>> groupResolversByPriority(Model model, List<String> resolvers) {
432+
TreeMap<Integer, List<String>> resolverGroups = new TreeMap<>();
433+
for (String resolverName : resolvers) {
434+
Integer priority = model.resolvers().get(resolverName).priority();
435+
if (!resolverGroups.containsKey(priority))
436+
resolverGroups.put(priority, new ArrayList<>());
437+
resolverGroups.get(priority).add(resolverName);
438+
}
439+
return resolverGroups;
440+
}
441+
423442
/**
424443
* Resets the variables that hold the state of the job, in case the same Job object is reused.
425444
*/
@@ -601,11 +620,61 @@ else if (size == 1)
601620
// Construct the resolvers clause.
602621
String resolversClause = "{}";
603622
TreeMap<String, TreeMap> resolversFilterTree = new TreeMap<>();
623+
TreeMap<Integer, TreeMap<String, TreeMap>> resolversFilterTreeGrouped= new TreeMap<>();
604624
if (!this.attributes.isEmpty()) {
605-
Map<String, Integer> counts = countAttributesAcrossResolvers(this.input.model(), resolvers);
606-
List<List<String>> resolversSorted = sortResolverAttributes(this.input.model(), resolvers, counts);
607-
resolversFilterTree = makeResolversFilterTree(resolversSorted);
608-
resolversClause = populateResolversFilterTree(this.input.model(), indexName, resolversFilterTree, this.attributes);
625+
626+
// Group the resolvers by their priority level.
627+
TreeMap<Integer, List<String>> resolverGroups = groupResolversByPriority(this.input.model(), resolvers);
628+
629+
// Construct a clause for each priority level in descending order of priority.
630+
List<Integer> priorities = new ArrayList<>(resolverGroups.keySet());
631+
Collections.reverse(priorities);
632+
for (int level = 0; level < priorities.size(); level++) {
633+
Integer priority = priorities.get(level);
634+
List<String> resolversGroup = resolverGroups.get(priority);
635+
Map<String, Integer> counts = countAttributesAcrossResolvers(this.input.model(), resolversGroup);
636+
List<List<String>> resolversSorted = sortResolverAttributes(this.input.model(), resolversGroup, counts);
637+
resolversFilterTree = makeResolversFilterTree(resolversSorted);
638+
resolversFilterTreeGrouped.put(level, resolversFilterTree);
639+
resolversClause = populateResolversFilterTree(this.input.model(), indexName, resolversFilterTree, this.attributes);
640+
641+
// If there are multiple levels of priority, then each lower priority group of resolvers must ensure
642+
// that every higher priority resolver either matches or does not exist.
643+
List<String> parentResolversClauses = new ArrayList<>();
644+
if (level > 0) {
645+
646+
// This is a lower priority group of resolvers.
647+
// Every higher priority resolver either must match or must not exist.
648+
for (int parentLevel = 0; parentLevel < level; parentLevel++) {
649+
Integer parentPriority = priorities.get(parentLevel);
650+
List<String> parentResolversGroup = resolverGroups.get(parentPriority);
651+
List<String> parentResolverClauses = new ArrayList<>();
652+
for (String parentResolverName : parentResolversGroup) {
653+
654+
// Construct a clause that checks if every attribute of the resolver does not exist.
655+
List<String> attributeExistsClauses = new ArrayList<>();
656+
for (String attributeName : this.input.model().resolvers().get(parentResolverName).attributes())
657+
attributeExistsClauses.add("{\"exists\":{\"field\":\"" + attributeName + "\"}}");
658+
String attributesExistsClause = "{\"bool\":{\"must_not\":[" + String.join(",", attributeExistsClauses) + "]}}";
659+
660+
// Construct a clause for the resolver.
661+
List<String> parentResolverGroup = new ArrayList<>(Arrays.asList(parentResolverName));
662+
Map<String, Integer> parentCounts = countAttributesAcrossResolvers(this.input.model(), parentResolverGroup);
663+
List<List<String>> parentResolverSorted = sortResolverAttributes(this.input.model(), parentResolverGroup, parentCounts);
664+
TreeMap<String, TreeMap> parentResolverFilterTree = makeResolversFilterTree(parentResolverSorted);
665+
String parentResolverClause = populateResolversFilterTree(this.input.model(), indexName, parentResolverFilterTree, this.attributes);
666+
667+
// Construct a "should" clause for the above two clauses.
668+
parentResolverClauses.add("{\"bool\":{\"should\":[" + attributesExistsClause + "," + parentResolverClause + "]}}");
669+
}
670+
671+
// Construct a "filter" clause for every higher priority resolver clause.
672+
parentResolversClauses.add("{\"bool\":{\"filter\":[" + String.join(",", parentResolverClauses) + "]}}");
673+
}
674+
}
675+
if (parentResolversClauses.size() > 0)
676+
resolversClause = "{\"bool\":{\"filter\":[" + resolversClause + "," + String.join(",", parentResolversClauses) + "]}}";
677+
}
609678
}
610679

611680
// Combine the ids clause and resolvers clause in a "should" clause if necessary.
@@ -661,7 +730,7 @@ else if (!idsClause.equals("{}"))
661730
responseDataCopyObjHits.remove("hits");
662731
}
663732
String resolversListLogged = Json.ORDERED_MAPPER.writeValueAsString(resolvers);
664-
String resolversFilterTreeLogged = Json.ORDERED_MAPPER.writeValueAsString(resolversFilterTree);
733+
String resolversFilterTreeLogged = Json.ORDERED_MAPPER.writeValueAsString(resolversFilterTreeGrouped);
665734
String resolversLogged = "{\"list\":" + resolversListLogged + ",\"tree\":" + resolversFilterTreeLogged + "}";
666735
String searchLogged = "{\"request\":" + query + ",\"response\":" + responseDataCopyObj + "}";
667736
String logged = "{\"_hop\":" + this.hop + ",\"_index\":\"" + indexName + "\",\"resolvers\":" + resolversLogged + ",\"search\":" + searchLogged + "}";

0 commit comments

Comments
 (0)