Sort: Make ScoreSortBuilder implement NamedWriteable and add fromXContent parsing

This change makes ScoreSortBuilder implement NamedWriteable, adds
equals() and hashCode() and also implements parsing ScoreSortBuilder
back from xContent. This is needed for the ongoing Search refactoring.
This commit is contained in:
Christoph Büscher 2016-03-08 15:06:10 +01:00
parent 7a53a396e4
commit 11b18a9963
5 changed files with 213 additions and 58 deletions

View File

@ -19,35 +19,116 @@
package org.elasticsearch.search.sort;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import java.io.IOException;
import java.util.Objects;
/**
* A sort builder allowing to sort by score.
*
*
*/
public class ScoreSortBuilder extends SortBuilder {
public class ScoreSortBuilder extends SortBuilder implements NamedWriteable<ScoreSortBuilder>,
SortElementParserTemp<ScoreSortBuilder> {
private SortOrder order;
private static final String NAME = "_score";
static final ScoreSortBuilder PROTOTYPE = new ScoreSortBuilder();
public static final ParseField REVERSE_FIELD = new ParseField("reverse");
public static final ParseField ORDER_FIELD = new ParseField("order");
private SortOrder order = SortOrder.DESC;
/**
* The order of sort scoring. By default, its {@link SortOrder#DESC}.
*/
@Override
public ScoreSortBuilder order(SortOrder order) {
Objects.requireNonNull(order, "sort order cannot be null.");
this.order = order;
return this;
}
/**
* Get the order of sort scoring. By default, its {@link SortOrder#DESC}.
*/
public SortOrder order() {
return this.order;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("_score");
builder.startObject(NAME);
if (order == SortOrder.ASC) {
builder.field("reverse", true);
builder.field(REVERSE_FIELD.getPreferredName(), true);
}
builder.endObject();
return builder;
}
@Override
public ScoreSortBuilder fromXContent(QueryParseContext context, String elementName) throws IOException {
XContentParser parser = context.parser();
ParseFieldMatcher matcher = context.parseFieldMatcher();
XContentParser.Token token;
String currentName = parser.currentName();
ScoreSortBuilder result = new ScoreSortBuilder();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentName = parser.currentName();
} else if (token.isValue()) {
if (matcher.match(currentName, REVERSE_FIELD)) {
if (parser.booleanValue()) {
result.order(SortOrder.ASC);
}
// else we keep the default DESC
} else if (matcher.match(currentName, ORDER_FIELD)) {
result.order(SortOrder.fromString(parser.text()));
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] failed to parse field [" + currentName + "]");
}
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] unexpected token [" + token + "]");
}
}
return result;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
ScoreSortBuilder other = (ScoreSortBuilder) object;
return Objects.equals(order, other.order);
}
@Override
public int hashCode() {
return Objects.hash(this.order);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
order.writeTo(out);
}
@Override
public ScoreSortBuilder readFrom(StreamInput in) throws IOException {
return new ScoreSortBuilder().order(SortOrder.readOrderFrom(in));
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -19,13 +19,12 @@
package org.elasticsearch.search.sort;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.index.query.QueryParseContext;
import java.io.IOException;
// TODO once sort refactoring is done this needs to be merged into SortBuilder
public interface SortElementParserTemp<T extends ToXContent> {
public interface SortElementParserTemp<T extends SortBuilder> {
/**
* Creates a new SortBuilder from the json held by the {@link SortElementParserTemp}
* in {@link org.elasticsearch.common.xcontent.XContent} format

View File

@ -43,7 +43,7 @@ import java.io.IOException;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXContent & SortElementParserTemp<T>> extends ESTestCase {
public abstract class AbstractSortTestCase<T extends SortBuilder & NamedWriteable<T> & SortElementParserTemp<T>> extends ESTestCase {
protected static NamedWriteableRegistry namedWriteableRegistry;
@ -53,7 +53,8 @@ public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXCont
@BeforeClass
public static void init() {
namedWriteableRegistry = new NamedWriteableRegistry();
namedWriteableRegistry.registerPrototype(GeoDistanceSortBuilder.class, GeoDistanceSortBuilder.PROTOTYPE);
namedWriteableRegistry.registerPrototype(SortBuilder.class, GeoDistanceSortBuilder.PROTOTYPE);
namedWriteableRegistry.registerPrototype(SortBuilder.class, ScoreSortBuilder.PROTOTYPE);
indicesQueriesRegistry = new SearchModule(Settings.EMPTY, namedWriteableRegistry).buildQueryParserRegistry();
}
@ -85,9 +86,9 @@ public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXCont
XContentParser itemParser = XContentHelper.createParser(builder.bytes());
itemParser.nextToken();
/*
* filter out name of sort, or field name to sort on for element fieldSort
* filter out name of sort, or field name to sort on for element fieldSort
*/
itemParser.nextToken();
String elementName = itemParser.currentName();
@ -95,7 +96,7 @@ public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXCont
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
context.reset(itemParser);
NamedWriteable<T> parsedItem = testItem.fromXContent(context, elementName);
SortBuilder parsedItem = testItem.fromXContent(context, elementName);
assertNotSame(testItem, parsedItem);
assertEquals(testItem, parsedItem);
assertEquals(testItem.hashCode(), parsedItem.hashCode());
@ -146,17 +147,15 @@ public abstract class AbstractSortTestCase<T extends NamedWriteable<T> & ToXCont
}
}
@SuppressWarnings("unchecked")
protected T copyItem(T original) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
original.writeTo(output);
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
@SuppressWarnings("unchecked")
T prototype = (T) namedWriteableRegistry.getPrototype(getPrototype(), original.getWriteableName());
T copy = (T) prototype.readFrom(in);
return copy;
T prototype = (T) namedWriteableRegistry.getPrototype(SortBuilder.class,
original.getWriteableName());
return prototype.readFrom(in);
}
}
}
protected abstract Class<T> getPrototype();
}

View File

@ -60,7 +60,7 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
break;
default:
throw new IllegalStateException("one of three geo initialisation strategies must be used");
}
if (randomBoolean()) {
result.geoDistance(geoDistance(result.geoDistance()));
@ -86,7 +86,7 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
if (randomBoolean()) {
result.ignoreMalformed(! result.ignoreMalformed());
}
return result;
}
@ -172,12 +172,6 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
}
@SuppressWarnings("unchecked")
@Override
protected Class<GeoDistanceSortBuilder> getPrototype() {
return (Class<GeoDistanceSortBuilder>) GeoDistanceSortBuilder.PROTOTYPE.getClass();
}
public void testSortModeSumIsRejectedInSetter() {
GeoDistanceSortBuilder builder = new GeoDistanceSortBuilder("testname", -1, -1);
GeoPoint point = RandomGeoGenerator.randomPoint(getRandom());
@ -189,23 +183,23 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
// all good
}
}
public void testSortModeSumIsRejectedInJSON() throws IOException {
String json = "{\n" +
" \"testname\" : [ {\n" +
" \"lat\" : -6.046997540714173,\n" +
" \"lon\" : -51.94128329747579\n" +
" } ],\n" +
" \"unit\" : \"m\",\n" +
" \"distance_type\" : \"sloppy_arc\",\n" +
" \"reverse\" : true,\n" +
" \"mode\" : \"SUM\",\n" +
" \"coerce\" : false,\n" +
" \"ignore_malformed\" : false\n" +
String json = "{\n" +
" \"testname\" : [ {\n" +
" \"lat\" : -6.046997540714173,\n" +
" \"lon\" : -51.94128329747579\n" +
" } ],\n" +
" \"unit\" : \"m\",\n" +
" \"distance_type\" : \"sloppy_arc\",\n" +
" \"reverse\" : true,\n" +
" \"mode\" : \"SUM\",\n" +
" \"coerce\" : false,\n" +
" \"ignore_malformed\" : false\n" +
"}";
XContentParser itemParser = XContentHelper.createParser(new BytesArray(json));
itemParser.nextToken();
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
context.reset(itemParser);
@ -216,28 +210,28 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
// all good
}
}
public void testGeoDistanceSortCanBeParsedFromGeoHash() throws IOException {
String json = "{\n" +
String json = "{\n" +
" \"VDcvDuFjE\" : [ \"7umzzv8eychg\", \"dmdgmt5z13uw\", " +
" \"ezu09wxw6v4c\", \"kc7s3515p6k6\", \"jgeuvjwrmfzn\", \"kcpcfj7ruyf8\" ],\n" +
" \"unit\" : \"m\",\n" +
" \"distance_type\" : \"sloppy_arc\",\n" +
" \"reverse\" : true,\n" +
" \"mode\" : \"MAX\",\n" +
" \"nested_filter\" : {\n" +
" \"ids\" : {\n" +
" \"type\" : [ ],\n" +
" \"values\" : [ ],\n" +
" \"boost\" : 5.711116\n" +
" }\n" +
" },\n" +
" \"coerce\" : false,\n" +
" \"ignore_malformed\" : true\n" +
" \"ezu09wxw6v4c\", \"kc7s3515p6k6\", \"jgeuvjwrmfzn\", \"kcpcfj7ruyf8\" ],\n" +
" \"unit\" : \"m\",\n" +
" \"distance_type\" : \"sloppy_arc\",\n" +
" \"reverse\" : true,\n" +
" \"mode\" : \"MAX\",\n" +
" \"nested_filter\" : {\n" +
" \"ids\" : {\n" +
" \"type\" : [ ],\n" +
" \"values\" : [ ],\n" +
" \"boost\" : 5.711116\n" +
" }\n" +
" },\n" +
" \"coerce\" : false,\n" +
" \"ignore_malformed\" : true\n" +
" }";
XContentParser itemParser = XContentHelper.createParser(new BytesArray(json));
itemParser.nextToken();
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
context.reset(itemParser);
@ -247,6 +241,6 @@ public class GeoDistanceSortBuilderTests extends AbstractSortTestCase<GeoDistanc
+ "43.71844606474042, -5.548660643398762, "
+ "-37.20467280596495, 38.71751043945551, "
+ "-69.44606635719538, 84.25200328230858, "
+ "-39.03717711567879, 44.74099852144718]", Arrays.toString(result.points()));
+ "-39.03717711567879, 44.74099852144718]", Arrays.toString(result.points()));
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.sort;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import java.io.IOException;
public class ScoreSortBuilderTests extends AbstractSortTestCase<ScoreSortBuilder> {
@Override
protected ScoreSortBuilder createTestItem() {
return new ScoreSortBuilder().order(randomBoolean() ? SortOrder.ASC : SortOrder.DESC);
}
@Override
protected ScoreSortBuilder mutate(ScoreSortBuilder original) throws IOException {
ScoreSortBuilder result = new ScoreSortBuilder();
if (original.order() == SortOrder.ASC) {
result.order(SortOrder.DESC);
} else {
result.order(SortOrder.ASC);
}
return result;
}
@Rule
public ExpectedException exceptionRule = ExpectedException.none();
/**
* test passing null to {@link ScoreSortBuilder#order(SortOrder)} is illegal
*/
public void testIllegalOrder() {
exceptionRule.expect(NullPointerException.class);
exceptionRule.expectMessage("sort order cannot be null.");
new ScoreSortBuilder().order(null);
}
/**
* test parsing order parameter if specified as `order` field in the json
* instead of the `reverse` field that we render in toXContent
*/
public void testParseOrder() throws IOException {
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
context.parseFieldMatcher(new ParseFieldMatcher(Settings.EMPTY));
SortOrder order = randomBoolean() ? SortOrder.ASC : SortOrder.DESC;
String scoreSortString = "{ \"_score\": { \"order\": \""+ order.toString() +"\" }}";
XContentParser parser = XContentFactory.xContent(scoreSortString).createParser(scoreSortString);
// need to skip until parser is located on second START_OBJECT
parser.nextToken();
parser.nextToken();
parser.nextToken();
context.reset(parser);
ScoreSortBuilder scoreSort = ScoreSortBuilder.PROTOTYPE.fromXContent(context, "_score");
assertEquals(order, scoreSort.order());
}
}