diff options
author | Areek Zillur <areek.zillur@elasticsearch.com> | 2016-02-10 16:21:24 -0500 |
---|---|---|
committer | Areek Zillur <areek.zillur@elasticsearch.com> | 2016-02-10 16:21:55 -0500 |
commit | 2038429f63cd31721c0522d2d49eab66303c68fb (patch) | |
tree | 1deac75ae674b0d33bfc45008347d6926f2b7c03 /core/src/main/java/org/elasticsearch/search/suggest/completion/context | |
parent | bbeb09eae7ac3c5d9837bb26eacfac6bba468929 (diff) |
initial refactoring of completion suggester
Diffstat (limited to 'core/src/main/java/org/elasticsearch/search/suggest/completion/context')
6 files changed, 254 insertions, 97 deletions
diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java index dffbb1aa80..10ac3935cc 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java @@ -158,12 +158,12 @@ public class CategoryContextMapping extends ContextMapping { List<QueryContext> queryContexts = new ArrayList<>(); Token token = parser.nextToken(); if (token == Token.START_OBJECT || token == Token.VALUE_STRING) { - CategoryQueryContext parse = CategoryQueryContext.parse(parser); - queryContexts.add(new QueryContext(parse.getCategory().toString(), parse.getBoost(), parse.isPrefix())); + CategoryQueryContext parse = CategoryQueryContext.PROTOTYPE.fromXContext(parser); + queryContexts.add(new QueryContext(parse.getCategory(), parse.getBoost(), parse.isPrefix())); } else if (token == Token.START_ARRAY) { while (parser.nextToken() != Token.END_ARRAY) { - CategoryQueryContext parse = CategoryQueryContext.parse(parser); - queryContexts.add(new QueryContext(parse.getCategory().toString(), parse.getBoost(), parse.isPrefix())); + CategoryQueryContext parse = CategoryQueryContext.PROTOTYPE.fromXContext(parser); + queryContexts.add(new QueryContext(parse.getCategory(), parse.getBoost(), parse.isPrefix())); } } return queryContexts; diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java index c493126577..8db9afe5ae 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java @@ -21,12 +21,15 @@ package org.elasticsearch.search.suggest.completion.context; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ObjectParser; -import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; +import java.util.Collections; +import java.util.Objects; import static org.elasticsearch.search.suggest.completion.context.CategoryContextMapping.CONTEXT_BOOST; import static org.elasticsearch.search.suggest.completion.context.CategoryContextMapping.CONTEXT_PREFIX; @@ -35,12 +38,15 @@ import static org.elasticsearch.search.suggest.completion.context.CategoryContex /** * Defines the query context for {@link CategoryContextMapping} */ -public final class CategoryQueryContext implements ToXContent { - private final CharSequence category; +public final class CategoryQueryContext implements QueryContext { + public static final String NAME = "category"; + public static final CategoryQueryContext PROTOTYPE = new CategoryQueryContext("", 1, false); + + private final String category; private final boolean isPrefix; private final int boost; - private CategoryQueryContext(CharSequence category, int boost, boolean isPrefix) { + private CategoryQueryContext(String category, int boost, boolean isPrefix) { this.category = category; this.boost = boost; this.isPrefix = isPrefix; @@ -49,7 +55,7 @@ public final class CategoryQueryContext implements ToXContent { /** * Returns the category of the context */ - public CharSequence getCategory() { + public String getCategory() { return category; } @@ -71,8 +77,81 @@ public final class CategoryQueryContext implements ToXContent { return new Builder(); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CategoryQueryContext that = (CategoryQueryContext) o; + + if (isPrefix != that.isPrefix) return false; + if (boost != that.boost) return false; + return category != null ? category.equals(that.category) : that.category == null; + + } + + @Override + public int hashCode() { + int result = category != null ? category.hashCode() : 0; + result = 31 * result + (isPrefix ? 1 : 0); + result = 31 * result + boost; + return result; + } + + @Override + public String getWriteableName() { + return NAME; + } + + private static ObjectParser<Builder, Void> CATEGORY_PARSER = new ObjectParser<>(NAME, null); + static { + CATEGORY_PARSER.declareString(Builder::setCategory, new ParseField(CONTEXT_VALUE)); + CATEGORY_PARSER.declareInt(Builder::setBoost, new ParseField(CONTEXT_BOOST)); + CATEGORY_PARSER.declareBoolean(Builder::setPrefix, new ParseField(CONTEXT_PREFIX)); + } + + @Override + public CategoryQueryContext fromXContext(XContentParser parser) throws IOException { + XContentParser.Token token = parser.currentToken(); + Builder builder = builder(); + if (token == XContentParser.Token.START_OBJECT) { + CATEGORY_PARSER.parse(parser, builder); + } else if (token == XContentParser.Token.VALUE_STRING) { + builder.setCategory(parser.text()); + } else { + throw new ElasticsearchParseException("category context must be an object or string"); + } + return builder.build(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(CONTEXT_VALUE, category); + builder.field(CONTEXT_BOOST, boost); + builder.field(CONTEXT_PREFIX, isPrefix); + builder.endObject(); + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeBoolean(isPrefix); + out.writeVInt(boost); + out.writeString(category); + } + + @Override + public QueryContext readFrom(StreamInput in) throws IOException { + Builder builder = new Builder(); + builder.isPrefix = in.readBoolean(); + builder.boost = in.readVInt(); + builder.category = in.readString(); + return builder.build(); + } + public static class Builder { - private CharSequence category; + private String category; private boolean isPrefix = false; private int boost = 1; @@ -80,11 +159,12 @@ public final class CategoryQueryContext implements ToXContent { } /** - * Sets the category of the context. + * Sets the category of the category. * This is a required field */ - public Builder setCategory(CharSequence context) { - this.category = context; + public Builder setCategory(String category) { + Objects.requireNonNull(category, "category must not be null"); + this.category = category; return this; } @@ -102,42 +182,16 @@ public final class CategoryQueryContext implements ToXContent { * Defaults to 1. */ public Builder setBoost(int boost) { + if (boost <= 0) { + throw new IllegalArgumentException("boost must be greater than 0"); + } this.boost = boost; return this; } public CategoryQueryContext build() { + Objects.requireNonNull(category, "category must not be null"); return new CategoryQueryContext(category, boost, isPrefix); } } - - private static ObjectParser<Builder, Void> CATEGORY_PARSER = new ObjectParser<>("category", null); - static { - CATEGORY_PARSER.declareString(Builder::setCategory, new ParseField("context")); - CATEGORY_PARSER.declareInt(Builder::setBoost, new ParseField("boost")); - CATEGORY_PARSER.declareBoolean(Builder::setPrefix, new ParseField("prefix")); - } - - public static CategoryQueryContext parse(XContentParser parser) throws IOException { - XContentParser.Token token = parser.currentToken(); - Builder builder = builder(); - if (token == XContentParser.Token.START_OBJECT) { - CATEGORY_PARSER.parse(parser, builder); - } else if (token == XContentParser.Token.VALUE_STRING) { - builder.setCategory(parser.text()); - } else { - throw new ElasticsearchParseException("category context must be an object or string"); - } - return builder.build(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field(CONTEXT_VALUE, category); - builder.field(CONTEXT_BOOST, boost); - builder.field(CONTEXT_PREFIX, isPrefix); - builder.endObject(); - return builder; - } } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java index 9d4bed4f66..ccd4b2d584 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java @@ -43,7 +43,6 @@ import java.util.Set; import static org.elasticsearch.search.suggest.completion.context.ContextMapping.FIELD_NAME; import static org.elasticsearch.search.suggest.completion.context.ContextMapping.FIELD_TYPE; -import static org.elasticsearch.search.suggest.completion.context.ContextMapping.QueryContext; import static org.elasticsearch.search.suggest.completion.context.ContextMapping.Type; /** @@ -153,7 +152,7 @@ public class ContextMappings implements ToXContent { * @param queryContexts a map of context mapping name and collected query contexts * @return a context-enabled query */ - public ContextQuery toContextQuery(CompletionQuery query, Map<String, List<QueryContext>> queryContexts) { + public ContextQuery toContextQuery(CompletionQuery query, Map<String, List<ContextMapping.QueryContext>> queryContexts) { ContextQuery typedContextQuery = new ContextQuery(query); if (queryContexts.isEmpty() == false) { CharsRefBuilder scratch = new CharsRefBuilder(); @@ -162,9 +161,9 @@ public class ContextMappings implements ToXContent { scratch.setCharAt(0, (char) typeId); scratch.setLength(1); ContextMapping mapping = contextMappings.get(typeId); - List<QueryContext> queryContext = queryContexts.get(mapping.name()); + List<ContextMapping.QueryContext> queryContext = queryContexts.get(mapping.name()); if (queryContext != null) { - for (QueryContext context : queryContext) { + for (ContextMapping.QueryContext context : queryContext) { scratch.append(context.context); typedContextQuery.addContext(scratch.toCharsRef(), context.boost, !context.isPrefix); scratch.setLength(1); diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java index f2f3d10215..2c90429302 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java @@ -247,18 +247,15 @@ public class GeoContextMapping extends ContextMapping { List<GeoQueryContext> queryContexts = new ArrayList<>(); Token token = parser.nextToken(); if (token == Token.START_OBJECT || token == Token.VALUE_STRING) { - queryContexts.add(GeoQueryContext.parse(parser)); + queryContexts.add(GeoQueryContext.PROTOTYPE.fromXContext(parser)); } else if (token == Token.START_ARRAY) { while (parser.nextToken() != Token.END_ARRAY) { - queryContexts.add(GeoQueryContext.parse(parser)); + queryContexts.add(GeoQueryContext.PROTOTYPE.fromXContext(parser)); } } List<QueryContext> queryContextList = new ArrayList<>(); for (GeoQueryContext queryContext : queryContexts) { - int minPrecision = this.precision; - if (queryContext.getPrecision() != -1) { - minPrecision = Math.min(minPrecision, queryContext.getPrecision()); - } + int minPrecision = Math.min(this.precision, queryContext.getPrecision()); GeoPoint point = queryContext.getGeoPoint(); final Collection<String> locations = new HashSet<>(); String geoHash = GeoHashUtils.stringEncode(point.getLon(), point.getLat(), minPrecision); diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java index da9191bf2d..5b406abc1d 100644 --- a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java @@ -23,14 +23,17 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoUtils; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ObjectParser; -import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.elasticsearch.search.suggest.completion.context.GeoContextMapping.CONTEXT_BOOST; import static org.elasticsearch.search.suggest.completion.context.GeoContextMapping.CONTEXT_NEIGHBOURS; @@ -40,7 +43,10 @@ import static org.elasticsearch.search.suggest.completion.context.GeoContextMapp /** * Defines the query context for {@link GeoContextMapping} */ -public final class GeoQueryContext implements ToXContent { +public final class GeoQueryContext implements QueryContext { + public static final String NAME = "geo"; + public static final GeoQueryContext PROTOTYPE = new GeoQueryContext(null, 1, 12, Collections.emptyList()); + private final GeoPoint geoPoint; private final int boost; private final int precision; @@ -81,14 +87,109 @@ public final class GeoQueryContext implements ToXContent { return neighbours; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GeoQueryContext that = (GeoQueryContext) o; + + if (boost != that.boost) return false; + if (precision != that.precision) return false; + if (geoPoint != null ? !geoPoint.equals(that.geoPoint) : that.geoPoint != null) return false; + return neighbours != null ? neighbours.equals(that.neighbours) : that.neighbours == null; + + } + + @Override + public int hashCode() { + int result = geoPoint != null ? geoPoint.hashCode() : 0; + result = 31 * result + boost; + result = 31 * result + precision; + result = 31 * result + (neighbours != null ? neighbours.hashCode() : 0); + return result; + } + public static Builder builder() { return new Builder(); } + @Override + public String getWriteableName() { + return NAME; + } + + private static ObjectParser<GeoQueryContext.Builder, Void> GEO_CONTEXT_PARSER = new ObjectParser<>(NAME, null); + static { + GEO_CONTEXT_PARSER.declareField((parser, geoQueryContext, geoContextMapping) -> geoQueryContext.setGeoPoint(GeoUtils.parseGeoPoint(parser)), new ParseField(CONTEXT_VALUE), ObjectParser.ValueType.OBJECT); + GEO_CONTEXT_PARSER.declareInt(GeoQueryContext.Builder::setBoost, new ParseField(CONTEXT_BOOST)); + // TODO : add string support for precision for GeoUtils.geoHashLevelsForPrecision() + GEO_CONTEXT_PARSER.declareInt(GeoQueryContext.Builder::setPrecision, new ParseField(CONTEXT_PRECISION)); + // TODO : add string array support for precision for GeoUtils.geoHashLevelsForPrecision() + GEO_CONTEXT_PARSER.declareIntArray(GeoQueryContext.Builder::setNeighbours, new ParseField(CONTEXT_NEIGHBOURS)); + GEO_CONTEXT_PARSER.declareDouble(GeoQueryContext.Builder::setLat, new ParseField("lat")); + GEO_CONTEXT_PARSER.declareDouble(GeoQueryContext.Builder::setLon, new ParseField("lon")); + } + + @Override + public GeoQueryContext fromXContext(XContentParser parser) throws IOException { + XContentParser.Token token = parser.currentToken(); + GeoQueryContext.Builder builder = new Builder(); + if (token == XContentParser.Token.START_OBJECT) { + GEO_CONTEXT_PARSER.parse(parser, builder); + } else if (token == XContentParser.Token.VALUE_STRING) { + builder.setGeoPoint(GeoPoint.fromGeohash(parser.text())); + } else { + throw new ElasticsearchParseException("geo context must be an object or string"); + } + return builder.build(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.startObject(CONTEXT_VALUE); + builder.field("lat", geoPoint.getLat()); + builder.field("lon", geoPoint.getLon()); + builder.endObject(); + builder.field(CONTEXT_BOOST, boost); + builder.field(CONTEXT_NEIGHBOURS, neighbours); + builder.field(CONTEXT_PRECISION, precision); + builder.endObject(); + return builder; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeGeoPoint(geoPoint); + out.writeVInt(boost); + out.writeInt(precision); + out.writeVInt(neighbours.size()); + for (Integer neighbour : neighbours) { + out.writeVInt(neighbour); + } + } + + @Override + public QueryContext readFrom(StreamInput in) throws IOException { + Builder builder = new Builder(); + builder.geoPoint = in.readGeoPoint(); + builder.boost = in.readVInt(); + builder.precision = in.readInt(); + int nNeighbour = in.readVInt(); + if (nNeighbour != 0) { + builder.neighbours = new ArrayList<>(nNeighbour); + for (int i = 0; i < nNeighbour; i++) { + builder.neighbours.add(in.readVInt()); + } + } + return builder.build(); + } + public static class Builder { private GeoPoint geoPoint; private int boost = 1; - private int precision = -1; + private int precision = 12; private List<Integer> neighbours = Collections.emptyList(); public Builder() { @@ -99,6 +200,9 @@ public final class GeoQueryContext implements ToXContent { * Defaults to 1 */ public Builder setBoost(int boost) { + if (boost <= 0) { + throw new IllegalArgumentException("boost must be greater than 0"); + } this.boost = boost; return this; } @@ -108,6 +212,9 @@ public final class GeoQueryContext implements ToXContent { * Defaults to using index-time precision level */ public Builder setPrecision(int precision) { + if (precision < 1 || precision > 12) { + throw new IllegalArgumentException("precision must be between 1 and 12"); + } this.precision = precision; return this; } @@ -117,6 +224,11 @@ public final class GeoQueryContext implements ToXContent { * Defaults to only considering neighbours at the index-time precision level */ public Builder setNeighbours(List<Integer> neighbours) { + for (int neighbour : neighbours) { + if (neighbour < 1 || neighbour > 12) { + throw new IllegalArgumentException("neighbour value must be between 1 and 12"); + } + } this.neighbours = neighbours; return this; } @@ -126,6 +238,7 @@ public final class GeoQueryContext implements ToXContent { * This is a required field */ public Builder setGeoPoint(GeoPoint geoPoint) { + Objects.requireNonNull(geoPoint, "geoPoint must not be null"); this.geoPoint = geoPoint; return this; } @@ -144,50 +257,10 @@ public final class GeoQueryContext implements ToXContent { if (geoPoint == null) { if (Double.isNaN(lat) == false && Double.isNaN(lon) == false) { geoPoint = new GeoPoint(lat, lon); - } else { - throw new IllegalArgumentException("no geohash or geo point provided"); } } + Objects.requireNonNull(geoPoint, "geoPoint must not be null"); return new GeoQueryContext(geoPoint, boost, precision, neighbours); } } - - private static ObjectParser<GeoQueryContext.Builder, Void> GEO_CONTEXT_PARSER = new ObjectParser<>("geo", null); - static { - GEO_CONTEXT_PARSER.declareField((parser, geoQueryContext, geoContextMapping) -> geoQueryContext.setGeoPoint(GeoUtils.parseGeoPoint(parser)), new ParseField("context"), ObjectParser.ValueType.OBJECT); - GEO_CONTEXT_PARSER.declareInt(GeoQueryContext.Builder::setBoost, new ParseField("boost")); - // TODO : add string support for precision for GeoUtils.geoHashLevelsForPrecision() - GEO_CONTEXT_PARSER.declareInt(GeoQueryContext.Builder::setPrecision, new ParseField("precision")); - // TODO : add string array support for precision for GeoUtils.geoHashLevelsForPrecision() - GEO_CONTEXT_PARSER.declareIntArray(GeoQueryContext.Builder::setNeighbours, new ParseField("neighbours")); - GEO_CONTEXT_PARSER.declareDouble(GeoQueryContext.Builder::setLat, new ParseField("lat")); - GEO_CONTEXT_PARSER.declareDouble(GeoQueryContext.Builder::setLon, new ParseField("lon")); - } - - public static GeoQueryContext parse(XContentParser parser) throws IOException { - XContentParser.Token token = parser.currentToken(); - GeoQueryContext.Builder builder = new Builder(); - if (token == XContentParser.Token.START_OBJECT) { - GEO_CONTEXT_PARSER.parse(parser, builder); - } else if (token == XContentParser.Token.VALUE_STRING) { - builder.setGeoPoint(GeoPoint.fromGeohash(parser.text())); - } else { - throw new ElasticsearchParseException("geo context must be an object or string"); - } - return builder.build(); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.startObject(CONTEXT_VALUE); - builder.field("lat", geoPoint.getLat()); - builder.field("lon", geoPoint.getLon()); - builder.endObject(); - builder.field(CONTEXT_BOOST, boost); - builder.field(CONTEXT_NEIGHBOURS, neighbours); - builder.field(CONTEXT_PRECISION, precision); - builder.endObject(); - return builder; - } } diff --git a/core/src/main/java/org/elasticsearch/search/suggest/completion/context/QueryContext.java b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/QueryContext.java new file mode 100644 index 0000000000..ccfd4a8d3d --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/suggest/completion/context/QueryContext.java @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.suggest.completion.context; + +import org.elasticsearch.common.io.stream.NamedWriteable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; + +/** + * Interface for serializing/de-serializing completion query context + */ +public interface QueryContext extends ToXContent, NamedWriteable<QueryContext> { + + QueryContext fromXContext(XContentParser parser) throws IOException; +} |