summaryrefslogtreecommitdiff
path: root/core/src/main/java/org/elasticsearch/search/suggest/completion/context
diff options
context:
space:
mode:
authorAreek Zillur <areek.zillur@elasticsearch.com>2016-02-10 16:21:24 -0500
committerAreek Zillur <areek.zillur@elasticsearch.com>2016-02-10 16:21:55 -0500
commit2038429f63cd31721c0522d2d49eab66303c68fb (patch)
tree1deac75ae674b0d33bfc45008347d6926f2b7c03 /core/src/main/java/org/elasticsearch/search/suggest/completion/context
parentbbeb09eae7ac3c5d9837bb26eacfac6bba468929 (diff)
initial refactoring of completion suggester
Diffstat (limited to 'core/src/main/java/org/elasticsearch/search/suggest/completion/context')
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryContextMapping.java8
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/CategoryQueryContext.java132
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/ContextMappings.java7
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoContextMapping.java9
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/GeoQueryContext.java161
-rw-r--r--core/src/main/java/org/elasticsearch/search/suggest/completion/context/QueryContext.java34
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;
+}