summaryrefslogtreecommitdiffstats
path: root/predicate-search-core/src/main/java/com/yahoo/search/predicate/PredicateQueryParser.java
diff options
context:
space:
mode:
Diffstat (limited to 'predicate-search-core/src/main/java/com/yahoo/search/predicate/PredicateQueryParser.java')
-rw-r--r--predicate-search-core/src/main/java/com/yahoo/search/predicate/PredicateQueryParser.java132
1 files changed, 132 insertions, 0 deletions
diff --git a/predicate-search-core/src/main/java/com/yahoo/search/predicate/PredicateQueryParser.java b/predicate-search-core/src/main/java/com/yahoo/search/predicate/PredicateQueryParser.java
new file mode 100644
index 00000000000..6f5fac54354
--- /dev/null
+++ b/predicate-search-core/src/main/java/com/yahoo/search/predicate/PredicateQueryParser.java
@@ -0,0 +1,132 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.predicate;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import org.apache.commons.lang.ArrayUtils;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Parses predicate queries from JSON.
+ *
+ * Input JSON is assumed to have the following format:
+ * {
+ * "features": [
+ * {"k": "key-name", "v":"value", "s":"0xDEADBEEFDEADBEEF"}
+ * ],
+ * "rangeFeatures": [
+ * {"k": "key-name", "v":42, "s":"0xDEADBEEFDEADBEEF"}
+ * ]
+ * }
+ *
+ * @author bjorncs
+ */
+public class PredicateQueryParser {
+ private final JsonFactory factory = new JsonFactory();
+
+ @FunctionalInterface
+ public interface FeatureHandler<V> {
+ void accept(String key, V value, long subqueryBitmap);
+ }
+
+ /**
+ * Parses predicate query from JSON.
+ * @param json JSON input.
+ * @param featureHandler The handler is invoked when a feature is parsed.
+ * @param rangeFeatureHandler The handler is invoked when a range feature is parsed.
+ * @throws IllegalArgumentException If JSON is invalid.
+ */
+ public void parseJsonQuery(
+ String json, FeatureHandler<String> featureHandler, FeatureHandler<Long> rangeFeatureHandler)
+ throws IllegalArgumentException {
+
+ try (JsonParser parser = factory.createParser(json)) {
+ skipToken(parser, JsonToken.START_OBJECT);
+ while (parser.nextToken() != JsonToken.END_OBJECT) {
+ String fieldName = parser.getCurrentName();
+ switch (fieldName) {
+ case "features":
+ parseFeatures(parser, JsonParser::getText, featureHandler);
+ break;
+ case "rangeFeatures":
+ parseFeatures(parser, JsonParser::getLongValue, rangeFeatureHandler);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid field name: " + fieldName);
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (IOException e) {
+ throw new AssertionError("This should never happen when parsing from a String", e);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(String.format("Parsing query from JSON failed: '%s'", json), e);
+ }
+ }
+
+ private static <V> void parseFeatures(
+ JsonParser parser, ValueParser<V> valueParser, FeatureHandler<V> featureHandler) throws IOException {
+ skipToken(parser, JsonToken.START_ARRAY);
+ while (parser.nextToken() != JsonToken.END_ARRAY) {
+ parseFeature(parser, valueParser, featureHandler);
+ }
+ }
+
+ private static <V> void parseFeature(
+ JsonParser parser, ValueParser<V> valueParser, FeatureHandler<V> featureHandler) throws IOException {
+ String key = null;
+ V value = null;
+ long subqueryBitmap = SubqueryBitmap.DEFAULT_VALUE; // Specifying subquery bitmap is optional.
+
+ while (parser.nextToken() != JsonToken.END_OBJECT) {
+ String fieldName = parser.getCurrentName();
+ skipToken(parser, JsonToken.VALUE_STRING, JsonToken.VALUE_NUMBER_INT);
+ switch (fieldName) {
+ case "k":
+ key = parser.getText();
+ break;
+ case "v":
+ value = valueParser.parse(parser);
+ break;
+ case "s":
+ subqueryBitmap = fromHexString(parser.getText());
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid field name: " + fieldName);
+ }
+ }
+ if (key == null) {
+ throw new IllegalArgumentException(
+ String.format("Feature key is missing! (%s)", parser.getCurrentLocation()));
+ }
+ if (value == null) {
+ throw new IllegalArgumentException(
+ String.format("Feature value is missing! (%s)", parser.getCurrentLocation()));
+ }
+ featureHandler.accept(key, value, subqueryBitmap);
+ }
+
+ private static void skipToken(JsonParser parser, JsonToken... expected) throws IOException {
+ JsonToken actual = parser.nextToken();
+ if (!ArrayUtils.contains(expected, actual)) {
+ throw new IllegalArgumentException(
+ String.format("Expected a token in %s, got %s (%s).",
+ Arrays.toString(expected), actual, parser.getTokenLocation()));
+ }
+ }
+
+ private static long fromHexString(String subqueryBitmap) {
+ if (!subqueryBitmap.startsWith("0x")) {
+ throw new IllegalArgumentException("Not a valid subquery bitmap ('0x' prefix missing): " + subqueryBitmap);
+ }
+ return Long.parseUnsignedLong(subqueryBitmap.substring(2), 16);
+ }
+
+ @FunctionalInterface
+ private interface ValueParser<V> {
+ V parse(JsonParser parser) throws IOException;
+ }
+}