Skip to content
Permalink
Browse files

Added an optional "terms" field to the resolution input which allows …

…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 1f3e452599be63ebbe68d42297f3248d3e978071
@@ -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.

@@ -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>
@@ -6,6 +6,7 @@

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\\.(.+)");

Large diffs are not rendered by default.

@@ -22,6 +22,7 @@
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;
@@ -195,6 +196,31 @@ public static Model includeResolvers(Model model, Set<String> resolvers) throws
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.
*
@@ -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.");
@@ -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.");
@@ -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")) {
@@ -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());
}
@@ -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(); }
}
@@ -64,6 +64,7 @@ public String serialized() {
return this.serialized;
}

@Override
public int compareTo(Value o) {
return this.serialized.compareTo(o.serialized);
}
@@ -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.
You can’t perform that action at this time.