Skip to content

Commit

Permalink
Added an optional "terms" field to the resolution input which allows …
Browse files Browse the repository at this point in the history
…resolutions jobs to be submitted with unknown or unstructured input values. Renamed "queries"."resolvers" to "queries"."filters" and changed its structure in the resolution response.
  • Loading branch information
davemoore- committed Jul 26, 2019
1 parent 048a21e commit 1f3e452
Show file tree
Hide file tree
Showing 10 changed files with 1,207 additions and 18 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Once you have installed Elasticsearch, you can install zentity from a remote URL

Example:

`elasticsearch-plugin install https://zentity.io/releases/zentity-1.3.1-elasticsearch-7.2.0.zip`
`elasticsearch-plugin install https://zentity.io/releases/zentity-1.4.0-beta1-elasticsearch-7.2.0.zip`

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

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<zentity.author>Dave Moore</zentity.author>
<zentity.classname>org.elasticsearch.plugin.zentity.ZentityPlugin</zentity.classname>
<zentity.website>https://zentity.io</zentity.website>
<zentity.version>1.3.1</zentity.version>
<zentity.version>1.4.0-beta1</zentity.version>
<!-- dependency versions -->
<elasticsearch.version>7.2.0</elasticsearch.version>
<jackson.core.version>2.9.9</jackson.core.version>
Expand Down
1 change: 1 addition & 0 deletions src/main/java/io/zentity/common/Patterns.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public class Patterns {

public static final Pattern COLON = Pattern.compile(":");
public static final Pattern EMPTY_STRING = Pattern.compile("^\\s*$");
public static final Pattern NUMBER_STRING = Pattern.compile("^-?\\d*\\.{0,1}\\d+$");
public static final Pattern PERIOD = Pattern.compile("\\.");
public static final Pattern VARIABLE = Pattern.compile("\\{\\{\\s*([^\\s{}]+)\\s*}}");
public static final Pattern VARIABLE_PARAMS = Pattern.compile("^params\\.(.+)");
Expand Down
197 changes: 188 additions & 9 deletions src/main/java/io/zentity/resolution/Job.java

Large diffs are not rendered by default.

44 changes: 39 additions & 5 deletions src/main/java/io/zentity/resolution/input/Input.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class Input {
private Map<String, Set<String>> ids = new TreeMap<>();
private Model model;
private Scope scope = new Scope();
private Set<Term> terms = new TreeSet<>();

public Input(JsonNode json, Model model) throws ValidationException, IOException {
this.model = model;
Expand Down Expand Up @@ -195,6 +196,31 @@ public static Map<String, Attribute> parseAttributes(JsonNode requestBody, Model
return attributesObj;
}

/**
* Parse and validate the "terms" field of the request body.
*
* @param requestBody The request body.
* @return The parsed "terms" field from the request body.
* @throws ValidationException
*/
public static Set<Term> parseTerms(JsonNode requestBody) throws ValidationException {
Set<Term> terms = new TreeSet<>();
if (!requestBody.has("terms") || requestBody.get("terms").size() == 0)
return terms;
if (requestBody.get("terms").isArray()) {
Iterator<JsonNode> termsNode = requestBody.get("terms").elements();
while (termsNode.hasNext()) {
JsonNode termNode = termsNode.next();
if (!termNode.isTextual())
throw new ValidationException("'terms' must be an array of strings.");
terms.add(new Term(termNode.asText()));
}
} else if (!requestBody.get("terms").isNull()) {
throw new ValidationException("'terms' must be an object or an array of strings.");
}
return terms;
}

/**
* Parse and validate the entity model from the 'model' field of the request body.
*
Expand Down Expand Up @@ -257,6 +283,10 @@ public Scope scope() {
return this.scope;
}

public Set<Term> terms() {
return this.terms;
}

public void deserialize(JsonNode json) throws ValidationException, IOException {
if (!json.isObject())
throw new ValidationException("Input must be an object.");
Expand All @@ -271,6 +301,7 @@ public void deserialize(JsonNode json) throws ValidationException, IOException {
case "ids":
case "model":
case "scope":
case "terms":
break;
default:
throw new ValidationException("'" + name + "' is not a recognized field.");
Expand All @@ -289,12 +320,15 @@ public void deserialize(JsonNode json) throws ValidationException, IOException {
// Parse and validate the "attributes" field of the request body.
this.attributes = parseAttributes(json, this.model);

// Parse and validate the "terms" field of the request body.
this.terms = parseTerms(json);

// Parse and validate the "ids" field of the request body.
this.ids = parseIds(json, this.model);

// Ensure that either the "attributes" or "ids" field exists and is valid.
if (this.attributes().isEmpty() && this.ids.isEmpty())
throw new ValidationException("The 'attributes' and 'ids' fields are missing from the request body. At least one must exist.");
// Ensure that either the "attributes" or "terms" or "ids" field exists and is valid.
if (this.attributes().isEmpty() && this.terms.isEmpty() && this.ids.isEmpty())
throw new ValidationException("The 'attributes', 'terms', and 'ids' fields are missing from the request body. At least one must exist.");

// Parse and validate the "scope" field of the request body.
if (json.has("scope")) {
Expand All @@ -303,11 +337,11 @@ public void deserialize(JsonNode json) throws ValidationException, IOException {
// Parse and validate the "scope"."include" field of the request body.
if (this.scope.include() != null) {

// Remove any resolvers entity model that do not appear in "scope.include.resolvers".
// Remove any resolvers of the entity model that do not appear in "scope.include.resolvers".
if (!this.scope.include().resolvers().isEmpty())
this.model = includeResolvers(this.model, this.scope.include().resolvers());

// Remove any indices entity model that do not appear in "scope.include.indices".
// Remove any indices of the entity model that do not appear in "scope.include.indices".
if (!this.scope.include().indices().isEmpty())
this.model = includeIndices(this.model, this.scope.include().indices());
}
Expand Down
166 changes: 166 additions & 0 deletions src/main/java/io/zentity/resolution/input/Term.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package io.zentity.resolution.input;

import com.fasterxml.jackson.databind.JsonNode;
import io.zentity.common.Json;
import io.zentity.common.Patterns;
import io.zentity.model.ValidationException;
import io.zentity.resolution.input.value.BooleanValue;
import io.zentity.resolution.input.value.DateValue;
import io.zentity.resolution.input.value.NumberValue;
import io.zentity.resolution.input.value.StringValue;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Term implements Comparable<Term> {

private final String term;
private Boolean isBoolean;
private Boolean isDate;
private Boolean isNumber;
private BooleanValue booleanValue;
private DateValue dateValue;
private NumberValue numberValue;
private StringValue stringValue;

public Term(String term) throws ValidationException {
validateTerm(term);
this.term = term;
}

private void validateTerm(String term) throws ValidationException {
if (Patterns.EMPTY_STRING.matcher(term).matches())
throw new ValidationException("A term must be a non-empty string.");
}

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

public static boolean isBoolean(String term) {
String termLowerCase = term.toLowerCase();
return termLowerCase.equals("true") || termLowerCase.equals("false");
}

public static boolean isDate(String term, String format) {
try {
SimpleDateFormat formatter = new SimpleDateFormat(format);
formatter.setLenient(false);
formatter.parse(term);
} catch (ParseException e) {
return false;
}
return true;
}

public static boolean isNumber(String term) {
return Patterns.NUMBER_STRING.matcher(term).matches();
}

/**
* Check if the term string is a boolean value.
* Lazily store the decision and then return the decision.
*
* @return
*/
public boolean isBoolean() {
if (this.isBoolean == null)
this.isBoolean = isBoolean(this.term);
return this.isBoolean;
}

/**
* Check if the term string is a date value.
* Lazily store the decision and then return the decision.
*
* @return
*/
public boolean isDate(String format) {
if (this.isDate == null)
this.isDate = isDate(this.term, format);
return this.isDate;
}

/**
* Convert the term to a BooleanValue.
* Lazily store the value and then return it.
*
* @return
*/
public BooleanValue booleanValue() throws IOException, ValidationException {
if (this.booleanValue == null) {
JsonNode value = Json.MAPPER.readTree("{\"value\":" + this.term + "}").get("value");
this.booleanValue = new BooleanValue(value);
}
return this.booleanValue;
}

/**
* Check if the term string is a number value.
* Lazily store the decision and then return the decision.
*
* @return
*/
public boolean isNumber() {
if (this.isNumber == null)
this.isNumber = isNumber(this.term);
return this.isNumber;
}

/**
* Convert the term to a DateValue.
* Lazily store the value and then return it.
*
* @return
*/
public DateValue dateValue() throws IOException, ValidationException {
if (this.dateValue == null) {
JsonNode value = Json.MAPPER.readTree("{\"value\":" + Json.quoteString(this.term) + "}").get("value");
this.dateValue = new DateValue(value);
}
return this.dateValue;
}

/**
* Convert the term to a NumberValue.
* Lazily store the value and then return it.
*
* @return
*/
public NumberValue numberValue() throws IOException, ValidationException {
if (this.numberValue == null) {
JsonNode value = Json.MAPPER.readTree("{\"value\":" + this.term + "}").get("value");
this.numberValue = new NumberValue(value);
}
return this.numberValue;
}

/**
* Convert the term to a StringValue.
* Lazily store the value and then return it.
*
* @return
*/
public StringValue stringValue() throws IOException, ValidationException {
if (this.stringValue == null) {
JsonNode value = Json.MAPPER.readTree("{\"value\":" + Json.quoteString(this.term) + "}").get("value");
this.stringValue = new StringValue(value);
}
return this.stringValue;
}

@Override
public int compareTo(Term o) {
return this.term.compareTo(o.term);
}

@Override
public String toString() {
return this.term;
}

@Override
public boolean equals(Object o) { return this.hashCode() == o.hashCode(); }

@Override
public int hashCode() { return this.term.hashCode(); }
}
7 changes: 7 additions & 0 deletions src/main/java/io/zentity/resolution/input/value/Value.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public String serialized() {
return this.serialized;
}

@Override
public int compareTo(Value o) {
return this.serialized.compareTo(o.serialized);
}
Expand All @@ -73,4 +74,10 @@ public String toString() {
return this.serialized;
}

@Override
public boolean equals(Object o) { return this.hashCode() == o.hashCode(); }

@Override
public int hashCode() { return this.serialized.hashCode(); }

}

0 comments on commit 1f3e452

Please sign in to comment.