Add fromXContent method to HighlightBuilder

For the search refactoring the HighlightBuilder needs a way to
create new instances by parsing xContent. For bwc this PR start
by moving over and slightly modifying the parsing from
HighlighterParseElement and keeps parsing for top level highlighter
and field options separate. Also adding tests for roundtrip
of random builder (rendering it to xContent and parsing it and
making sure the original builder properties are preserved)
This commit is contained in:
Christoph Büscher 2015-11-26 19:45:33 +01:00
parent 5402277462
commit aa69c4a20b
4 changed files with 289 additions and 14 deletions

View File

@ -377,7 +377,7 @@ public abstract class AbstractHighlighterBuilder<HB extends AbstractHighlighterB
builder.field("boundary_max_scan", boundaryMaxScan);
}
if (boundaryChars != null) {
builder.field("boundary_chars", boundaryChars);
builder.field("boundary_chars", new String(boundaryChars));
}
if (options != null && options.size() > 0) {
builder.field("options", options);

View File

@ -26,6 +26,8 @@ import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import java.io.IOException;
import java.util.ArrayList;
@ -170,6 +172,114 @@ public class HighlightBuilder extends AbstractHighlighterBuilder<HighlightBuilde
return builder;
}
/**
* Creates a new {@link HighlightBuilder} from the highlighter held by the {@link QueryParseContext}
* in {@link org.elasticsearch.common.xcontent.XContent} format
*
* @param parseContext
* the input parse context. The state on the parser contained in
* this context will be changed as a side effect of this method
* call
* @return the new {@link HighlightBuilder}
*/
public static HighlightBuilder fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
XContentParser.Token token;
String topLevelFieldName = null;
HighlightBuilder highlightBuilder = new HighlightBuilder();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
topLevelFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_ARRAY) {
if ("pre_tags".equals(topLevelFieldName) || "preTags".equals(topLevelFieldName)) {
List<String> preTagsList = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
preTagsList.add(parser.text());
}
highlightBuilder.preTags(preTagsList.toArray(new String[preTagsList.size()]));
} else if ("post_tags".equals(topLevelFieldName) || "postTags".equals(topLevelFieldName)) {
List<String> postTagsList = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
postTagsList.add(parser.text());
}
highlightBuilder.postTags(postTagsList.toArray(new String[postTagsList.size()]));
} else if ("fields".equals(topLevelFieldName)) {
highlightBuilder.useExplicitFieldOrder(true);
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
if (token == XContentParser.Token.START_OBJECT) {
String highlightFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
if (highlightFieldName != null) {
throw new IllegalArgumentException("If highlighter fields is an array it must contain objects containing a single field");
}
highlightFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
highlightBuilder.field(Field.fromXContent(highlightFieldName, parseContext));
}
}
} else {
throw new IllegalArgumentException("If highlighter fields is an array it must contain objects containing a single field");
}
}
}
} else if (token.isValue()) {
if ("order".equals(topLevelFieldName)) {
highlightBuilder.order(parser.text());
} else if ("tags_schema".equals(topLevelFieldName) || "tagsSchema".equals(topLevelFieldName)) {
highlightBuilder.tagsSchema(parser.text());
} else if ("highlight_filter".equals(topLevelFieldName) || "highlightFilter".equals(topLevelFieldName)) {
highlightBuilder.highlightFilter(parser.booleanValue());
} else if ("fragment_size".equals(topLevelFieldName) || "fragmentSize".equals(topLevelFieldName)) {
highlightBuilder.fragmentSize(parser.intValue());
} else if ("number_of_fragments".equals(topLevelFieldName) || "numberOfFragments".equals(topLevelFieldName)) {
highlightBuilder.numOfFragments(parser.intValue());
} else if ("encoder".equals(topLevelFieldName)) {
highlightBuilder.encoder(parser.text());
} else if ("require_field_match".equals(topLevelFieldName) || "requireFieldMatch".equals(topLevelFieldName)) {
highlightBuilder.requireFieldMatch(parser.booleanValue());
} else if ("boundary_max_scan".equals(topLevelFieldName) || "boundaryMaxScan".equals(topLevelFieldName)) {
highlightBuilder.boundaryMaxScan(parser.intValue());
} else if ("boundary_chars".equals(topLevelFieldName) || "boundaryChars".equals(topLevelFieldName)) {
highlightBuilder.boundaryChars(parser.text().toCharArray());
} else if ("type".equals(topLevelFieldName)) {
highlightBuilder.highlighterType(parser.text());
} else if ("fragmenter".equals(topLevelFieldName)) {
highlightBuilder.fragmenter(parser.text());
} else if ("no_match_size".equals(topLevelFieldName) || "noMatchSize".equals(topLevelFieldName)) {
highlightBuilder.noMatchSize(parser.intValue());
} else if ("force_source".equals(topLevelFieldName) || "forceSource".equals(topLevelFieldName)) {
highlightBuilder.forceSource(parser.booleanValue());
} else if ("phrase_limit".equals(topLevelFieldName) || "phraseLimit".equals(topLevelFieldName)) {
highlightBuilder.phraseLimit(parser.intValue());
}
} else if (token == XContentParser.Token.START_OBJECT && "options".equals(topLevelFieldName)) {
highlightBuilder.options(parser.map());
} else if (token == XContentParser.Token.START_OBJECT) {
if ("fields".equals(topLevelFieldName)) {
String highlightFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
highlightFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
highlightBuilder.field(Field.fromXContent(highlightFieldName, parseContext));
}
}
} else if ("highlight_query".equals(topLevelFieldName) || "highlightQuery".equals(topLevelFieldName)) {
highlightBuilder.highlightQuery(parseContext.parseInnerQueryBuilder());
}
}
}
if (highlightBuilder.preTags() != null && highlightBuilder.postTags() == null) {
throw new IllegalArgumentException("Highlighter global preTags are set, but global postTags are not set");
}
return highlightBuilder;
}
public void innerXContent(XContentBuilder builder) throws IOException {
// first write common options
commonOptionsToXContent(builder);
@ -205,7 +315,7 @@ public class HighlightBuilder extends AbstractHighlighterBuilder<HighlightBuilde
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.prettyPrint();
toXContent(builder, ToXContent.EMPTY_PARAMS);
toXContent(builder, EMPTY_PARAMS);
return builder.string();
} catch (Exception e) {
return "{ \"error\" : \"" + ExceptionsHelper.detailedMessage(e) + "\"}";
@ -294,6 +404,74 @@ public class HighlightBuilder extends AbstractHighlighterBuilder<HighlightBuilde
builder.endObject();
}
private static HighlightBuilder.Field fromXContent(String fieldname, QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
XContentParser.Token token;
final HighlightBuilder.Field field = new HighlightBuilder.Field(fieldname);
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_ARRAY) {
if ("pre_tags".equals(currentFieldName) || "preTags".equals(currentFieldName)) {
List<String> preTagsList = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
preTagsList.add(parser.text());
}
field.preTags(preTagsList.toArray(new String[preTagsList.size()]));
} else if ("post_tags".equals(currentFieldName) || "postTags".equals(currentFieldName)) {
List<String> postTagsList = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
postTagsList.add(parser.text());
}
field.postTags(postTagsList.toArray(new String[postTagsList.size()]));
} else if ("matched_fields".equals(currentFieldName) || "matchedFields".equals(currentFieldName)) {
List<String> matchedFields = new ArrayList<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
matchedFields.add(parser.text());
}
field.matchedFields(matchedFields.toArray(new String[matchedFields.size()]));
}
} else if (token.isValue()) {
if ("fragment_size".equals(currentFieldName) || "fragmentSize".equals(currentFieldName)) {
field.fragmentSize(parser.intValue());
} else if ("number_of_fragments".equals(currentFieldName) || "numberOfFragments".equals(currentFieldName)) {
field.numOfFragments(parser.intValue());
} else if ("fragment_offset".equals(currentFieldName) || "fragmentOffset".equals(currentFieldName)) {
field.fragmentOffset(parser.intValue());
} else if ("highlight_filter".equals(currentFieldName) || "highlightFilter".equals(currentFieldName)) {
field.highlightFilter(parser.booleanValue());
} else if ("order".equals(currentFieldName)) {
field.order(parser.text());
} else if ("require_field_match".equals(currentFieldName) || "requireFieldMatch".equals(currentFieldName)) {
field.requireFieldMatch(parser.booleanValue());
} else if ("boundary_max_scan".equals(currentFieldName) || "boundaryMaxScan".equals(currentFieldName)) {
field.boundaryMaxScan(parser.intValue());
} else if ("boundary_chars".equals(currentFieldName) || "boundaryChars".equals(currentFieldName)) {
field.boundaryChars(parser.text().toCharArray());
} else if ("type".equals(currentFieldName)) {
field.highlighterType(parser.text());
} else if ("fragmenter".equals(currentFieldName)) {
field.fragmenter(parser.text());
} else if ("no_match_size".equals(currentFieldName) || "noMatchSize".equals(currentFieldName)) {
field.noMatchSize(parser.intValue());
} else if ("force_source".equals(currentFieldName) || "forceSource".equals(currentFieldName)) {
field.forceSource(parser.booleanValue());
} else if ("phrase_limit".equals(currentFieldName) || "phraseLimit".equals(currentFieldName)) {
field.phraseLimit(parser.intValue());
}
} else if (token == XContentParser.Token.START_OBJECT) {
if ("highlight_query".equals(currentFieldName) || "highlightQuery".equals(currentFieldName)) {
field.highlightQuery(parseContext.parseInnerQueryBuilder());
} else if ("options".equals(currentFieldName)) {
field.options(parser.map());
}
}
}
return field;
}
@Override
protected int doHashCode() {
return Objects.hash(name, fragmentOffset, Arrays.hashCode(matchedFields));

View File

@ -94,7 +94,7 @@ public class HighlighterParseElement implements SearchParseElement {
}
}
public SearchContextHighlight parse(XContentParser parser, QueryShardContext queryShardContext) throws IOException {
public static SearchContextHighlight parse(XContentParser parser, QueryShardContext queryShardContext) throws IOException {
XContentParser.Token token;
String topLevelFieldName = null;
final List<Tuple<String, SearchContextHighlight.FieldOptions.Builder>> fieldsOptions = new ArrayList<>();
@ -211,7 +211,7 @@ public class HighlighterParseElement implements SearchParseElement {
return new SearchContextHighlight(fields);
}
protected SearchContextHighlight.FieldOptions.Builder parseFields(XContentParser parser, QueryShardContext queryShardContext) throws IOException {
private static SearchContextHighlight.FieldOptions.Builder parseFields(XContentParser parser, QueryShardContext queryShardContext) throws IOException {
XContentParser.Token token;
final SearchContextHighlight.FieldOptions.Builder fieldOptionsBuilder = new SearchContextHighlight.FieldOptions.Builder();

View File

@ -23,10 +23,23 @@ import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.IdsQueryBuilder;
import org.elasticsearch.index.query.IdsQueryParser;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryParser;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.TermQueryParser;
import org.elasticsearch.indices.query.IndicesQueriesRegistry;
import org.elasticsearch.search.highlight.HighlightBuilder.Field;
import org.elasticsearch.test.ESTestCase;
import org.junit.AfterClass;
@ -35,8 +48,10 @@ import org.junit.BeforeClass;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
@ -45,23 +60,26 @@ public class HighlightBuilderTests extends ESTestCase {
private static final int NUMBER_OF_TESTBUILDERS = 20;
private static NamedWriteableRegistry namedWriteableRegistry;
private static IndicesQueriesRegistry indicesQueriesRegistry;
/**
* setup for the whole base test class
*/
@BeforeClass
public static void init() {
if (namedWriteableRegistry == null) {
namedWriteableRegistry = new NamedWriteableRegistry();
namedWriteableRegistry.registerPrototype(QueryBuilder.class, new MatchAllQueryBuilder());
namedWriteableRegistry.registerPrototype(QueryBuilder.class, new IdsQueryBuilder());
namedWriteableRegistry.registerPrototype(QueryBuilder.class, new TermQueryBuilder("field", "value"));
}
@SuppressWarnings("rawtypes")
Set<QueryParser> injectedQueryParsers = new HashSet<>();
injectedQueryParsers.add(new MatchAllQueryParser());
injectedQueryParsers.add(new IdsQueryParser());
injectedQueryParsers.add(new TermQueryParser());
indicesQueriesRegistry = new IndicesQueriesRegistry(Settings.settingsBuilder().build(), injectedQueryParsers, namedWriteableRegistry);
}
@AfterClass
public static void afterClass() throws Exception {
namedWriteableRegistry = null;
indicesQueriesRegistry = null;
}
/**
@ -107,6 +125,83 @@ public class HighlightBuilderTests extends ESTestCase {
}
}
/**
* Generic test that creates new highlighter from the test highlighter and checks both for equality
*/
public void testFromXContent() throws IOException {
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
HighlightBuilder highlightBuilder = randomHighlighterBuilder();
XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
if (randomBoolean()) {
builder.prettyPrint();
}
builder.startObject();
highlightBuilder.innerXContent(builder);
builder.endObject();
XContentParser parser = XContentHelper.createParser(builder.bytes());
context.reset(parser);
HighlightBuilder secondHighlightBuilder = HighlightBuilder.fromXContent(context);
assertNotSame(highlightBuilder, secondHighlightBuilder);
assertEquals(highlightBuilder, secondHighlightBuilder);
assertEquals(highlightBuilder.hashCode(), secondHighlightBuilder.hashCode());
}
}
/**
* `tags_schema` is not produced by toXContent in the builder but should be parseable, so this
* adds a simple json test for this.
*/
public void testParsingTagsSchema() throws IOException {
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry);
String highlightElement = "{\n" +
" \"tags_schema\" : \"styled\"\n" +
"}\n";
XContentParser parser = XContentFactory.xContent(highlightElement).createParser(highlightElement);
context.reset(parser);
HighlightBuilder highlightBuilder = HighlightBuilder.fromXContent(context);
assertArrayEquals("setting tags_schema 'styled' should alter pre_tags", HighlighterParseElement.STYLED_PRE_TAG,
highlightBuilder.preTags());
assertArrayEquals("setting tags_schema 'styled' should alter post_tags", HighlighterParseElement.STYLED_POST_TAGS,
highlightBuilder.postTags());
highlightElement = "{\n" +
" \"tags_schema\" : \"default\"\n" +
"}\n";
parser = XContentFactory.xContent(highlightElement).createParser(highlightElement);
context.reset(parser);
highlightBuilder = HighlightBuilder.fromXContent(context);
assertArrayEquals("setting tags_schema 'default' should alter pre_tags", HighlighterParseElement.DEFAULT_PRE_TAGS,
highlightBuilder.preTags());
assertArrayEquals("setting tags_schema 'default' should alter post_tags", HighlighterParseElement.DEFAULT_POST_TAGS,
highlightBuilder.postTags());
highlightElement = "{\n" +
" \"tags_schema\" : \"somthing_else\"\n" +
"}\n";
parser = XContentFactory.xContent(highlightElement).createParser(highlightElement);
context.reset(parser);
try {
highlightBuilder = HighlightBuilder.fromXContent(context);
fail("setting unknown tag schema should throw exception");
} catch (IllegalArgumentException e) {
assertEquals("Unknown tag schema [somthing_else]", e.getMessage());
}
}
protected static XContentBuilder toXContent(HighlightBuilder highlight, XContentType contentType) throws IOException {
XContentBuilder builder = XContentFactory.contentBuilder(contentType);
if (randomBoolean()) {
builder.prettyPrint();
}
highlight.toXContent(builder, ToXContent.EMPTY_PARAMS);
return builder;
}
/**
* create random shape that is put under test
*/
@ -132,11 +227,11 @@ public class HighlightBuilderTests extends ESTestCase {
return testHighlighter;
}
@SuppressWarnings("rawtypes")
private static void setRandomCommonOptions(AbstractHighlighterBuilder highlightBuilder) {
if (randomBoolean()) {
// need to set this together, otherwise parsing will complain
highlightBuilder.preTags(randomStringArray(0, 3));
}
if (randomBoolean()) {
highlightBuilder.postTags(randomStringArray(0, 3));
}
if (randomBoolean()) {
@ -213,7 +308,7 @@ public class HighlightBuilderTests extends ESTestCase {
}
}
@SuppressWarnings("unchecked")
@SuppressWarnings({ "unchecked", "rawtypes" })
private static void mutateCommonOptions(AbstractHighlighterBuilder highlightBuilder) {
switch (randomIntBetween(1, 16)) {
case 1:
@ -242,6 +337,7 @@ public class HighlightBuilderTests extends ESTestCase {
break;
case 9:
highlightBuilder.highlightFilter(toggleOrSet(highlightBuilder.highlightFilter()));
break;
case 10:
highlightBuilder.forceSource(toggleOrSet(highlightBuilder.forceSource()));
break;
@ -316,6 +412,7 @@ public class HighlightBuilderTests extends ESTestCase {
fieldToChange.matchedFields(randomStringArray(5, 10));
}
}
break;
}
}
return mutation;