Refactor HighlighterBuilder
This change pulls out the common fields that HighlighterBuilder shares with its nested Field class into a new abstract CommonHighlighterOptions superclass which also gets equals() and hashCode() method and methods to serialize the common fields to a StreamOutput and read them from a stream. Relates to #15044
This commit is contained in:
parent
a0fe93fa67
commit
027a9b1844
|
@ -408,7 +408,7 @@ public final class SearchSourceBuilder extends ToXContentToBytes implements Writ
|
|||
try {
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
builder.startObject();
|
||||
highlightBuilder.innerXContent(builder, EMPTY_PARAMS);
|
||||
highlightBuilder.innerXContent(builder);
|
||||
builder.endObject();
|
||||
this.highlightBuilder = builder.bytes();
|
||||
return this;
|
||||
|
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
* 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.highlight;
|
||||
|
||||
import org.apache.lucene.search.highlight.SimpleFragmenter;
|
||||
import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This abstract class holds parameters shared by {@link HighlightBuilder} and {@link HighlightBuilder.Field}
|
||||
* and provides the common setters, equality, hashCode calculation and common serialization
|
||||
*/
|
||||
public abstract class AbstractHighlighterBuilder<HB extends AbstractHighlighterBuilder> {
|
||||
|
||||
protected String[] preTags;
|
||||
|
||||
protected String[] postTags;
|
||||
|
||||
protected Integer fragmentSize;
|
||||
|
||||
protected Integer numOfFragments;
|
||||
|
||||
protected String highlighterType;
|
||||
|
||||
protected String fragmenter;
|
||||
|
||||
protected QueryBuilder highlightQuery;
|
||||
|
||||
protected String order;
|
||||
|
||||
protected Boolean highlightFilter;
|
||||
|
||||
protected Boolean forceSource;
|
||||
|
||||
protected Integer boundaryMaxScan;
|
||||
|
||||
protected char[] boundaryChars;
|
||||
|
||||
protected Integer noMatchSize;
|
||||
|
||||
protected Integer phraseLimit;
|
||||
|
||||
protected Map<String, Object> options;
|
||||
|
||||
protected Boolean requireFieldMatch;
|
||||
|
||||
/**
|
||||
* Set the pre tags that will be used for highlighting.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB preTags(String... preTags) {
|
||||
this.preTags = preTags;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #preTags(String...)}
|
||||
*/
|
||||
public String[] preTags() {
|
||||
return this.preTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the post tags that will be used for highlighting.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB postTags(String... postTags) {
|
||||
this.postTags = postTags;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #postTags(String...)}
|
||||
*/
|
||||
public String[] postTags() {
|
||||
return this.postTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fragment size in characters, defaults to {@link HighlighterParseElement#DEFAULT_FRAGMENT_CHAR_SIZE}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB fragmentSize(Integer fragmentSize) {
|
||||
this.fragmentSize = fragmentSize;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #fragmentSize(Integer)}
|
||||
*/
|
||||
public Integer fragmentSize() {
|
||||
return this.fragmentSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of fragments, defaults to {@link HighlighterParseElement#DEFAULT_NUMBER_OF_FRAGMENTS}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB numOfFragments(Integer numOfFragments) {
|
||||
this.numOfFragments = numOfFragments;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #numOfFragments(Integer)}
|
||||
*/
|
||||
public Integer numOfFragments() {
|
||||
return this.numOfFragments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set type of highlighter to use. Out of the box supported types
|
||||
* are <tt>plain</tt>, <tt>fvh</tt> and <tt>postings</tt>.
|
||||
* The default option selected is dependent on the mappings defined for your index.
|
||||
* Details of the different highlighter types are covered in the reference guide.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB highlighterType(String highlighterType) {
|
||||
this.highlighterType = highlighterType;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #highlighterType(String)}
|
||||
*/
|
||||
public String highlighterType() {
|
||||
return this.highlighterType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets what fragmenter to use to break up text that is eligible for highlighting.
|
||||
* This option is only applicable when using the plain highlighterType <tt>highlighter</tt>.
|
||||
* Permitted values are "simple" or "span" relating to {@link SimpleFragmenter} and
|
||||
* {@link SimpleSpanFragmenter} implementations respectively with the default being "span"
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB fragmenter(String fragmenter) {
|
||||
this.fragmenter = fragmenter;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #fragmenter(String)}
|
||||
*/
|
||||
public String fragmenter() {
|
||||
return this.fragmenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a query to be used for highlighting instead of the search query.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB highlightQuery(QueryBuilder highlightQuery) {
|
||||
this.highlightQuery = highlightQuery;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #highlightQuery(QueryBuilder)}
|
||||
*/
|
||||
public QueryBuilder highlightQuery() {
|
||||
return this.highlightQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* The order of fragments per field. By default, ordered by the order in the
|
||||
* highlighted text. Can be <tt>score</tt>, which then it will be ordered
|
||||
* by score of the fragments.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB order(String order) {
|
||||
this.order = order;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #order(String)}
|
||||
*/
|
||||
public String order() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true when using the highlighterType <tt>fvh</tt>
|
||||
* and you want to provide highlighting on filter clauses in your
|
||||
* query. Default is <tt>false</tt>.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB highlightFilter(Boolean highlightFilter) {
|
||||
this.highlightFilter = highlightFilter;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #highlightFilter(Boolean)}
|
||||
*/
|
||||
public Boolean highlightFilter() {
|
||||
return this.highlightFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* When using the highlighterType <tt>fvh</tt> this setting
|
||||
* controls how far to look for boundary characters, and defaults to 20.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB boundaryMaxScan(Integer boundaryMaxScan) {
|
||||
this.boundaryMaxScan = boundaryMaxScan;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #boundaryMaxScan(Integer)}
|
||||
*/
|
||||
public Integer boundaryMaxScan() {
|
||||
return this.boundaryMaxScan;
|
||||
}
|
||||
|
||||
/**
|
||||
* When using the highlighterType <tt>fvh</tt> this setting
|
||||
* defines what constitutes a boundary for highlighting. It’s a single string with
|
||||
* each boundary character defined in it. It defaults to .,!? \t\n
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB boundaryChars(char[] boundaryChars) {
|
||||
this.boundaryChars = boundaryChars;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #boundaryChars(char[])}
|
||||
*/
|
||||
public char[] boundaryChars() {
|
||||
return this.boundaryChars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to set custom options for custom highlighters.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB options(Map<String, Object> options) {
|
||||
this.options = options;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #options(Map)}
|
||||
*/
|
||||
public Map<String, Object> options() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to cause a field to be highlighted only if a query matches that field.
|
||||
* Default is false meaning that terms are highlighted on all requested fields regardless
|
||||
* if the query matches specifically on them.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB requireFieldMatch(Boolean requireFieldMatch) {
|
||||
this.requireFieldMatch = requireFieldMatch;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #requireFieldMatch(Boolean)}
|
||||
*/
|
||||
public Boolean requireFieldMatch() {
|
||||
return this.requireFieldMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the fragment to return from the beginning of the field if there are no matches to
|
||||
* highlight and the field doesn't also define noMatchSize.
|
||||
* @param noMatchSize integer to set or null to leave out of request. default is null.
|
||||
* @return this for chaining
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB noMatchSize(Integer noMatchSize) {
|
||||
this.noMatchSize = noMatchSize;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #noMatchSize(Integer)}
|
||||
*/
|
||||
public Integer noMatchSize() {
|
||||
return this.noMatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of phrases the fvh will consider if the field doesn't also define phraseLimit.
|
||||
* @param phraseLimit maximum number of phrases the fvh will consider
|
||||
* @return this for chaining
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB phraseLimit(Integer phraseLimit) {
|
||||
this.phraseLimit = phraseLimit;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #phraseLimit(Integer)}
|
||||
*/
|
||||
public Integer phraseLimit() {
|
||||
return this.noMatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the highlighting to highlight fields based on the source even if fields are stored separately.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public HB forceSource(Boolean forceSource) {
|
||||
this.forceSource = forceSource;
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value set by {@link #forceSource(Boolean)}
|
||||
*/
|
||||
public Boolean forceSource() {
|
||||
return this.forceSource;
|
||||
}
|
||||
|
||||
void commonOptionsToXContent(XContentBuilder builder) throws IOException {
|
||||
if (preTags != null) {
|
||||
builder.array("pre_tags", preTags);
|
||||
}
|
||||
if (postTags != null) {
|
||||
builder.array("post_tags", postTags);
|
||||
}
|
||||
if (fragmentSize != null) {
|
||||
builder.field("fragment_size", fragmentSize);
|
||||
}
|
||||
if (numOfFragments != null) {
|
||||
builder.field("number_of_fragments", numOfFragments);
|
||||
}
|
||||
if (highlighterType != null) {
|
||||
builder.field("type", highlighterType);
|
||||
}
|
||||
if (fragmenter != null) {
|
||||
builder.field("fragmenter", fragmenter);
|
||||
}
|
||||
if (highlightQuery != null) {
|
||||
builder.field("highlight_query", highlightQuery);
|
||||
}
|
||||
if (order != null) {
|
||||
builder.field("order", order);
|
||||
}
|
||||
if (highlightFilter != null) {
|
||||
builder.field("highlight_filter", highlightFilter);
|
||||
}
|
||||
if (boundaryMaxScan != null) {
|
||||
builder.field("boundary_max_scan", boundaryMaxScan);
|
||||
}
|
||||
if (boundaryChars != null) {
|
||||
builder.field("boundary_chars", boundaryChars);
|
||||
}
|
||||
if (options != null && options.size() > 0) {
|
||||
builder.field("options", options);
|
||||
}
|
||||
if (forceSource != null) {
|
||||
builder.field("force_source", forceSource);
|
||||
}
|
||||
if (requireFieldMatch != null) {
|
||||
builder.field("require_field_match", requireFieldMatch);
|
||||
}
|
||||
if (noMatchSize != null) {
|
||||
builder.field("no_match_size", noMatchSize);
|
||||
}
|
||||
if (phraseLimit != null) {
|
||||
builder.field("phrase_limit", phraseLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return Objects.hash(getClass(), Arrays.hashCode(preTags), Arrays.hashCode(postTags), fragmentSize,
|
||||
numOfFragments, highlighterType, fragmenter, highlightQuery, order, highlightFilter,
|
||||
forceSource, boundaryMaxScan, Arrays.hashCode(boundaryChars), noMatchSize,
|
||||
phraseLimit, options, requireFieldMatch, doHashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* internal hashCode calculation to overwrite for the implementing classes.
|
||||
*/
|
||||
protected abstract int doHashCode();
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
HB other = (HB) obj;
|
||||
return Arrays.equals(preTags, other.preTags) &&
|
||||
Arrays.equals(postTags, other.postTags) &&
|
||||
Objects.equals(fragmentSize, other.fragmentSize) &&
|
||||
Objects.equals(numOfFragments, other.numOfFragments) &&
|
||||
Objects.equals(highlighterType, other.highlighterType) &&
|
||||
Objects.equals(fragmenter, other.fragmenter) &&
|
||||
Objects.equals(highlightQuery, other.highlightQuery) &&
|
||||
Objects.equals(order, other.order) &&
|
||||
Objects.equals(highlightFilter, other.highlightFilter) &&
|
||||
Objects.equals(forceSource, other.forceSource) &&
|
||||
Objects.equals(boundaryMaxScan, other.boundaryMaxScan) &&
|
||||
Arrays.equals(boundaryChars, other.boundaryChars) &&
|
||||
Objects.equals(noMatchSize, other.noMatchSize) &&
|
||||
Objects.equals(phraseLimit, other.phraseLimit) &&
|
||||
Objects.equals(options, other.options) &&
|
||||
Objects.equals(requireFieldMatch, other.requireFieldMatch) &&
|
||||
doEquals(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* internal equals to overwrite for the implementing classes.
|
||||
*/
|
||||
protected abstract boolean doEquals(HB other);
|
||||
|
||||
/**
|
||||
* read common parameters from {@link StreamInput}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected HB readOptionsFrom(StreamInput in) throws IOException {
|
||||
preTags(in.readOptionalStringArray());
|
||||
postTags(in.readOptionalStringArray());
|
||||
fragmentSize(in.readOptionalVInt());
|
||||
numOfFragments(in.readOptionalVInt());
|
||||
highlighterType(in.readOptionalString());
|
||||
fragmenter(in.readOptionalString());
|
||||
if (in.readBoolean()) {
|
||||
highlightQuery(in.readQuery());
|
||||
}
|
||||
order(in.readOptionalString());
|
||||
highlightFilter(in.readOptionalBoolean());
|
||||
forceSource(in.readOptionalBoolean());
|
||||
boundaryMaxScan(in.readOptionalVInt());
|
||||
if (in.readBoolean()) {
|
||||
boundaryChars(in.readString().toCharArray());
|
||||
}
|
||||
noMatchSize(in.readOptionalVInt());
|
||||
phraseLimit(in.readOptionalVInt());
|
||||
if (in.readBoolean()) {
|
||||
options(in.readMap());
|
||||
}
|
||||
requireFieldMatch(in.readOptionalBoolean());
|
||||
return (HB) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* write common parameters to {@link StreamOutput}
|
||||
*/
|
||||
protected void writeOptionsTo(StreamOutput out) throws IOException {
|
||||
out.writeOptionalStringArray(preTags);
|
||||
out.writeOptionalStringArray(postTags);
|
||||
out.writeOptionalVInt(fragmentSize);
|
||||
out.writeOptionalVInt(numOfFragments);
|
||||
out.writeOptionalString(highlighterType);
|
||||
out.writeOptionalString(fragmenter);
|
||||
boolean hasQuery = highlightQuery != null;
|
||||
out.writeBoolean(hasQuery);
|
||||
if (hasQuery) {
|
||||
out.writeQuery(highlightQuery);
|
||||
}
|
||||
out.writeOptionalString(order);
|
||||
out.writeOptionalBoolean(highlightFilter);
|
||||
out.writeOptionalBoolean(forceSource);
|
||||
out.writeOptionalVInt(boundaryMaxScan);
|
||||
boolean hasBounaryChars = boundaryChars != null;
|
||||
out.writeBoolean(hasBounaryChars);
|
||||
if (hasBounaryChars) {
|
||||
out.writeString(String.valueOf(boundaryChars));
|
||||
}
|
||||
out.writeOptionalVInt(noMatchSize);
|
||||
out.writeOptionalVInt(phraseLimit);
|
||||
boolean hasOptions = options != null;
|
||||
out.writeBoolean(hasOptions);
|
||||
if (hasOptions) {
|
||||
out.writeMap(options);
|
||||
}
|
||||
out.writeOptionalBoolean(requireFieldMatch);
|
||||
}
|
||||
}
|
|
@ -19,16 +19,19 @@
|
|||
|
||||
package org.elasticsearch.search.highlight;
|
||||
|
||||
import org.apache.lucene.search.highlight.SimpleFragmenter;
|
||||
import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A builder for search highlighting. Settings can control how large fields
|
||||
|
@ -36,46 +39,14 @@ import java.util.Map;
|
|||
*
|
||||
* @see org.elasticsearch.search.builder.SearchSourceBuilder#highlight()
|
||||
*/
|
||||
public class HighlightBuilder implements ToXContent {
|
||||
public class HighlightBuilder extends AbstractHighlighterBuilder<HighlightBuilder> implements Writeable<HighlightBuilder>, ToXContent {
|
||||
|
||||
private List<Field> fields;
|
||||
public static final HighlightBuilder PROTOTYPE = new HighlightBuilder();
|
||||
|
||||
private String tagsSchema;
|
||||
|
||||
private Boolean highlightFilter;
|
||||
|
||||
private Integer fragmentSize;
|
||||
|
||||
private Integer numOfFragments;
|
||||
|
||||
private String[] preTags;
|
||||
|
||||
private String[] postTags;
|
||||
|
||||
private String order;
|
||||
private final List<Field> fields = new ArrayList<>();
|
||||
|
||||
private String encoder;
|
||||
|
||||
private Boolean requireFieldMatch;
|
||||
|
||||
private Integer boundaryMaxScan;
|
||||
|
||||
private char[] boundaryChars;
|
||||
|
||||
private String highlighterType;
|
||||
|
||||
private String fragmenter;
|
||||
|
||||
private QueryBuilder highlightQuery;
|
||||
|
||||
private Integer noMatchSize;
|
||||
|
||||
private Integer phraseLimit;
|
||||
|
||||
private Map<String, Object> options;
|
||||
|
||||
private Boolean forceSource;
|
||||
|
||||
private boolean useExplicitFieldOrder = false;
|
||||
|
||||
/**
|
||||
|
@ -85,14 +56,9 @@ public class HighlightBuilder implements ToXContent {
|
|||
* @param name The field to highlight
|
||||
*/
|
||||
public HighlightBuilder field(String name) {
|
||||
if (fields == null) {
|
||||
fields = new ArrayList<>();
|
||||
}
|
||||
fields.add(new Field(name));
|
||||
return this;
|
||||
return field(new Field(name));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a field to be highlighted with a provided fragment size (in characters), and
|
||||
* default number of fragments of 5.
|
||||
|
@ -101,11 +67,7 @@ public class HighlightBuilder implements ToXContent {
|
|||
* @param fragmentSize The size of a fragment in characters
|
||||
*/
|
||||
public HighlightBuilder field(String name, int fragmentSize) {
|
||||
if (fields == null) {
|
||||
fields = new ArrayList<>();
|
||||
}
|
||||
fields.add(new Field(name).fragmentSize(fragmentSize));
|
||||
return this;
|
||||
return field(new Field(name).fragmentSize(fragmentSize));
|
||||
}
|
||||
|
||||
|
||||
|
@ -118,14 +80,9 @@ public class HighlightBuilder implements ToXContent {
|
|||
* @param numberOfFragments The (maximum) number of fragments
|
||||
*/
|
||||
public HighlightBuilder field(String name, int fragmentSize, int numberOfFragments) {
|
||||
if (fields == null) {
|
||||
fields = new ArrayList<>();
|
||||
}
|
||||
fields.add(new Field(name).fragmentSize(fragmentSize).numOfFragments(numberOfFragments));
|
||||
return this;
|
||||
return field(new Field(name).fragmentSize(fragmentSize).numOfFragments(numberOfFragments));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a field to be highlighted with a provided fragment size (in characters), and
|
||||
* a provided (maximum) number of fragments.
|
||||
|
@ -136,56 +93,38 @@ public class HighlightBuilder implements ToXContent {
|
|||
* @param fragmentOffset The offset from the start of the fragment to the start of the highlight
|
||||
*/
|
||||
public HighlightBuilder field(String name, int fragmentSize, int numberOfFragments, int fragmentOffset) {
|
||||
if (fields == null) {
|
||||
fields = new ArrayList<>();
|
||||
}
|
||||
fields.add(new Field(name).fragmentSize(fragmentSize).numOfFragments(numberOfFragments)
|
||||
return field(new Field(name).fragmentSize(fragmentSize).numOfFragments(numberOfFragments)
|
||||
.fragmentOffset(fragmentOffset));
|
||||
return this;
|
||||
}
|
||||
|
||||
public HighlightBuilder field(Field field) {
|
||||
if (fields == null) {
|
||||
fields = new ArrayList<>();
|
||||
}
|
||||
fields.add(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Field> fields() {
|
||||
return this.fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a tag scheme that encapsulates a built in pre and post tags. The allows schemes
|
||||
* Set a tag scheme that encapsulates a built in pre and post tags. The allowed schemes
|
||||
* are <tt>styled</tt> and <tt>default</tt>.
|
||||
*
|
||||
* @param schemaName The tag scheme name
|
||||
*/
|
||||
public HighlightBuilder tagsSchema(String schemaName) {
|
||||
this.tagsSchema = schemaName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true when using the highlighterType <tt>fvh</tt>
|
||||
* and you want to provide highlighting on filter clauses in your
|
||||
* query. Default is <tt>false</tt>.
|
||||
*/
|
||||
public HighlightBuilder highlightFilter(boolean highlightFilter) {
|
||||
this.highlightFilter = highlightFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of a fragment in characters (defaults to 100)
|
||||
*/
|
||||
public HighlightBuilder fragmentSize(Integer fragmentSize) {
|
||||
this.fragmentSize = fragmentSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of fragments returned
|
||||
*/
|
||||
public HighlightBuilder numOfFragments(Integer numOfFragments) {
|
||||
this.numOfFragments = numOfFragments;
|
||||
switch (schemaName) {
|
||||
case "default":
|
||||
preTags(HighlighterParseElement.DEFAULT_PRE_TAGS);
|
||||
postTags(HighlighterParseElement.DEFAULT_POST_TAGS);
|
||||
break;
|
||||
case "styled":
|
||||
preTags(HighlighterParseElement.STYLED_PRE_TAG);
|
||||
postTags(HighlighterParseElement.STYLED_POST_TAGS);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown tag schema ["+ schemaName +"]");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -201,125 +140,10 @@ public class HighlightBuilder implements ToXContent {
|
|||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the pre tags that will be used for highlighting.
|
||||
* Getter for {@link #encoder(String)}
|
||||
*/
|
||||
public HighlightBuilder preTags(String... preTags) {
|
||||
this.preTags = preTags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the post tags that will be used for highlighting.
|
||||
*/
|
||||
public HighlightBuilder postTags(String... postTags) {
|
||||
this.postTags = postTags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The order of fragments per field. By default, ordered by the order in the
|
||||
* highlighted text. Can be <tt>score</tt>, which then it will be ordered
|
||||
* by score of the fragments.
|
||||
*/
|
||||
public HighlightBuilder order(String order) {
|
||||
this.order = order;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to cause a field to be highlighted only if a query matches that field.
|
||||
* Default is false meaning that terms are highlighted on all requested fields regardless
|
||||
* if the query matches specifically on them.
|
||||
*/
|
||||
public HighlightBuilder requireFieldMatch(boolean requireFieldMatch) {
|
||||
this.requireFieldMatch = requireFieldMatch;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When using the highlighterType <tt>fvh</tt> this setting
|
||||
* controls how far to look for boundary characters, and defaults to 20.
|
||||
*/
|
||||
public HighlightBuilder boundaryMaxScan(Integer boundaryMaxScan) {
|
||||
this.boundaryMaxScan = boundaryMaxScan;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When using the highlighterType <tt>fvh</tt> this setting
|
||||
* defines what constitutes a boundary for highlighting. It’s a single string with
|
||||
* each boundary character defined in it. It defaults to .,!? \t\n
|
||||
*/
|
||||
public HighlightBuilder boundaryChars(char[] boundaryChars) {
|
||||
this.boundaryChars = boundaryChars;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set type of highlighter to use. Out of the box supported types
|
||||
* are <tt>plain</tt>, <tt>fvh</tt> and <tt>postings</tt>.
|
||||
* The default option selected is dependent on the mappings defined for your index.
|
||||
* Details of the different highlighter types are covered in the reference guide.
|
||||
*/
|
||||
public HighlightBuilder highlighterType(String highlighterType) {
|
||||
this.highlighterType = highlighterType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets what fragmenter to use to break up text that is eligible for highlighting.
|
||||
* This option is only applicable when using the plain highlighterType <tt>highlighter</tt>.
|
||||
* Permitted values are "simple" or "span" relating to {@link SimpleFragmenter} and
|
||||
* {@link SimpleSpanFragmenter} implementations respectively with the default being "span"
|
||||
*/
|
||||
public HighlightBuilder fragmenter(String fragmenter) {
|
||||
this.fragmenter = fragmenter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a query to be used for highlighting all fields instead of the search query.
|
||||
*/
|
||||
public HighlightBuilder highlightQuery(QueryBuilder highlightQuery) {
|
||||
this.highlightQuery = highlightQuery;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the fragment to return from the beginning of the field if there are no matches to
|
||||
* highlight and the field doesn't also define noMatchSize.
|
||||
* @param noMatchSize integer to set or null to leave out of request. default is null.
|
||||
* @return this for chaining
|
||||
*/
|
||||
public HighlightBuilder noMatchSize(Integer noMatchSize) {
|
||||
this.noMatchSize = noMatchSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of phrases the fvh will consider if the field doesn't also define phraseLimit.
|
||||
* @param phraseLimit maximum number of phrases the fvh will consider
|
||||
* @return this for chaining
|
||||
*/
|
||||
public HighlightBuilder phraseLimit(Integer phraseLimit) {
|
||||
this.phraseLimit = phraseLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to set custom options for custom highlighters.
|
||||
*/
|
||||
public HighlightBuilder options(Map<String, Object> options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the highlighting to highlight fields based on the source even if fields are stored separately.
|
||||
*/
|
||||
public HighlightBuilder forceSource(boolean forceSource) {
|
||||
this.forceSource = forceSource;
|
||||
return this;
|
||||
public String encoder() {
|
||||
return this.encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -331,71 +155,29 @@ public class HighlightBuilder implements ToXContent {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets value set with {@link #useExplicitFieldOrder(boolean)}
|
||||
*/
|
||||
public Boolean useExplicitFieldOrder() {
|
||||
return this.useExplicitFieldOrder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject("highlight");
|
||||
innerXContent(builder, params);
|
||||
innerXContent(builder);
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
public void innerXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
if (tagsSchema != null) {
|
||||
builder.field("tags_schema", tagsSchema);
|
||||
}
|
||||
if (preTags != null) {
|
||||
builder.array("pre_tags", preTags);
|
||||
}
|
||||
if (postTags != null) {
|
||||
builder.array("post_tags", postTags);
|
||||
}
|
||||
if (order != null) {
|
||||
builder.field("order", order);
|
||||
}
|
||||
if (highlightFilter != null) {
|
||||
builder.field("highlight_filter", highlightFilter);
|
||||
}
|
||||
if (fragmentSize != null) {
|
||||
builder.field("fragment_size", fragmentSize);
|
||||
}
|
||||
if (numOfFragments != null) {
|
||||
builder.field("number_of_fragments", numOfFragments);
|
||||
}
|
||||
public void innerXContent(XContentBuilder builder) throws IOException {
|
||||
// first write common options
|
||||
commonOptionsToXContent(builder);
|
||||
// special options for top-level highlighter
|
||||
if (encoder != null) {
|
||||
builder.field("encoder", encoder);
|
||||
}
|
||||
if (requireFieldMatch != null) {
|
||||
builder.field("require_field_match", requireFieldMatch);
|
||||
}
|
||||
if (boundaryMaxScan != null) {
|
||||
builder.field("boundary_max_scan", boundaryMaxScan);
|
||||
}
|
||||
if (boundaryChars != null) {
|
||||
builder.field("boundary_chars", boundaryChars);
|
||||
}
|
||||
if (highlighterType != null) {
|
||||
builder.field("type", highlighterType);
|
||||
}
|
||||
if (fragmenter != null) {
|
||||
builder.field("fragmenter", fragmenter);
|
||||
}
|
||||
if (highlightQuery != null) {
|
||||
builder.field("highlight_query", highlightQuery);
|
||||
}
|
||||
if (noMatchSize != null) {
|
||||
builder.field("no_match_size", noMatchSize);
|
||||
}
|
||||
if (phraseLimit != null) {
|
||||
builder.field("phrase_limit", phraseLimit);
|
||||
}
|
||||
if (options != null && options.size() > 0) {
|
||||
builder.field("options", options);
|
||||
}
|
||||
if (forceSource != null) {
|
||||
builder.field("force_source", forceSource);
|
||||
}
|
||||
if (fields != null) {
|
||||
if (fields.size() > 0) {
|
||||
if (useExplicitFieldOrder) {
|
||||
builder.startArray("fields");
|
||||
} else {
|
||||
|
@ -405,63 +187,7 @@ public class HighlightBuilder implements ToXContent {
|
|||
if (useExplicitFieldOrder) {
|
||||
builder.startObject();
|
||||
}
|
||||
builder.startObject(field.name());
|
||||
if (field.preTags != null) {
|
||||
builder.field("pre_tags", field.preTags);
|
||||
}
|
||||
if (field.postTags != null) {
|
||||
builder.field("post_tags", field.postTags);
|
||||
}
|
||||
if (field.fragmentSize != -1) {
|
||||
builder.field("fragment_size", field.fragmentSize);
|
||||
}
|
||||
if (field.numOfFragments != -1) {
|
||||
builder.field("number_of_fragments", field.numOfFragments);
|
||||
}
|
||||
if (field.fragmentOffset != -1) {
|
||||
builder.field("fragment_offset", field.fragmentOffset);
|
||||
}
|
||||
if (field.highlightFilter != null) {
|
||||
builder.field("highlight_filter", field.highlightFilter);
|
||||
}
|
||||
if (field.order != null) {
|
||||
builder.field("order", field.order);
|
||||
}
|
||||
if (field.requireFieldMatch != null) {
|
||||
builder.field("require_field_match", field.requireFieldMatch);
|
||||
}
|
||||
if (field.boundaryMaxScan != -1) {
|
||||
builder.field("boundary_max_scan", field.boundaryMaxScan);
|
||||
}
|
||||
if (field.boundaryChars != null) {
|
||||
builder.field("boundary_chars", field.boundaryChars);
|
||||
}
|
||||
if (field.highlighterType != null) {
|
||||
builder.field("type", field.highlighterType);
|
||||
}
|
||||
if (field.fragmenter != null) {
|
||||
builder.field("fragmenter", field.fragmenter);
|
||||
}
|
||||
if (field.highlightQuery != null) {
|
||||
builder.field("highlight_query", field.highlightQuery);
|
||||
}
|
||||
if (field.noMatchSize != null) {
|
||||
builder.field("no_match_size", field.noMatchSize);
|
||||
}
|
||||
if (field.matchedFields != null) {
|
||||
builder.field("matched_fields", field.matchedFields);
|
||||
}
|
||||
if (field.phraseLimit != null) {
|
||||
builder.field("phrase_limit", field.phraseLimit);
|
||||
}
|
||||
if (field.options != null && field.options.size() > 0) {
|
||||
builder.field("options", field.options);
|
||||
}
|
||||
if (field.forceSource != null) {
|
||||
builder.field("force_source", field.forceSource);
|
||||
}
|
||||
|
||||
builder.endObject();
|
||||
field.innerXContent(builder);
|
||||
if (useExplicitFieldOrder) {
|
||||
builder.endObject();
|
||||
}
|
||||
|
@ -474,26 +200,62 @@ public class HighlightBuilder implements ToXContent {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Field {
|
||||
final String name;
|
||||
String[] preTags;
|
||||
String[] postTags;
|
||||
int fragmentSize = -1;
|
||||
@Override
|
||||
public final String toString() {
|
||||
try {
|
||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
builder.prettyPrint();
|
||||
toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
return builder.string();
|
||||
} catch (Exception e) {
|
||||
return "{ \"error\" : \"" + ExceptionsHelper.detailedMessage(e) + "\"}";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doHashCode() {
|
||||
return Objects.hash(encoder, useExplicitFieldOrder, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doEquals(HighlightBuilder other) {
|
||||
return Objects.equals(encoder, other.encoder) &&
|
||||
Objects.equals(useExplicitFieldOrder, other.useExplicitFieldOrder) &&
|
||||
Objects.equals(fields, other.fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HighlightBuilder readFrom(StreamInput in) throws IOException {
|
||||
HighlightBuilder highlightBuilder = new HighlightBuilder();
|
||||
highlightBuilder.readOptionsFrom(in)
|
||||
.encoder(in.readOptionalString())
|
||||
.useExplicitFieldOrder(in.readBoolean());
|
||||
int fields = in.readVInt();
|
||||
for (int i = 0; i < fields; i++) {
|
||||
highlightBuilder.field(Field.PROTOTYPE.readFrom(in));
|
||||
}
|
||||
return highlightBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
writeOptionsTo(out);
|
||||
out.writeOptionalString(encoder);
|
||||
out.writeBoolean(useExplicitFieldOrder);
|
||||
out.writeVInt(fields.size());
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
fields.get(i).writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Field extends AbstractHighlighterBuilder<Field> implements Writeable<Field> {
|
||||
static final Field PROTOTYPE = new Field("_na_");
|
||||
|
||||
private final String name;
|
||||
|
||||
int fragmentOffset = -1;
|
||||
int numOfFragments = -1;
|
||||
Boolean highlightFilter;
|
||||
String order;
|
||||
Boolean requireFieldMatch;
|
||||
int boundaryMaxScan = -1;
|
||||
char[] boundaryChars;
|
||||
String highlighterType;
|
||||
String fragmenter;
|
||||
QueryBuilder highlightQuery;
|
||||
Integer noMatchSize;
|
||||
|
||||
String[] matchedFields;
|
||||
Integer phraseLimit;
|
||||
Map<String, Object> options;
|
||||
Boolean forceSource;
|
||||
|
||||
public Field(String name) {
|
||||
this.name = name;
|
||||
|
@ -503,118 +265,11 @@ public class HighlightBuilder implements ToXContent {
|
|||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the pre tags for this field that will be used for highlighting.
|
||||
* This overrides global settings set by {@link HighlightBuilder#preTags(String...)}.
|
||||
*/
|
||||
public Field preTags(String... preTags) {
|
||||
this.preTags = preTags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the post tags for this field that will be used for highlighting.
|
||||
* This overrides global settings set by {@link HighlightBuilder#postTags(String...)}.
|
||||
*/
|
||||
public Field postTags(String... postTags) {
|
||||
this.postTags = postTags;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Field fragmentSize(int fragmentSize) {
|
||||
this.fragmentSize = fragmentSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Field fragmentOffset(int fragmentOffset) {
|
||||
this.fragmentOffset = fragmentOffset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Field numOfFragments(int numOfFragments) {
|
||||
this.numOfFragments = numOfFragments;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Field highlightFilter(boolean highlightFilter) {
|
||||
this.highlightFilter = highlightFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The order of fragments per field. By default, ordered by the order in the
|
||||
* highlighted text. Can be <tt>score</tt>, which then it will be ordered
|
||||
* by score of the fragments.
|
||||
* This overrides global settings set by {@link HighlightBuilder#order(String)}.
|
||||
*/
|
||||
public Field order(String order) {
|
||||
this.order = order;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Field requireFieldMatch(boolean requireFieldMatch) {
|
||||
this.requireFieldMatch = requireFieldMatch;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Field boundaryMaxScan(int boundaryMaxScan) {
|
||||
this.boundaryMaxScan = boundaryMaxScan;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Field boundaryChars(char[] boundaryChars) {
|
||||
this.boundaryChars = boundaryChars;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set type of highlighter to use. Out of the box supported types
|
||||
* are <tt>plain</tt>, <tt>fvh</tt> and <tt>postings</tt>.
|
||||
* This overrides global settings set by {@link HighlightBuilder#highlighterType(String)}.
|
||||
*/
|
||||
public Field highlighterType(String highlighterType) {
|
||||
this.highlighterType = highlighterType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets what fragmenter to use to break up text that is eligible for highlighting.
|
||||
* This option is only applicable when using plain / normal highlighter.
|
||||
* This overrides global settings set by {@link HighlightBuilder#fragmenter(String)}.
|
||||
*/
|
||||
public Field fragmenter(String fragmenter) {
|
||||
this.fragmenter = fragmenter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a query to use for highlighting this field instead of the search query.
|
||||
*/
|
||||
public Field highlightQuery(QueryBuilder highlightQuery) {
|
||||
this.highlightQuery = highlightQuery;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the fragment to return from the beginning of the field if there are no matches to
|
||||
* highlight.
|
||||
* @param noMatchSize integer to set or null to leave out of request. default is null.
|
||||
* @return this for chaining
|
||||
*/
|
||||
public Field noMatchSize(Integer noMatchSize) {
|
||||
this.noMatchSize = noMatchSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to set custom options for custom highlighters.
|
||||
* This overrides global settings set by {@link HighlightBuilder#options(Map)}.
|
||||
*/
|
||||
public Field options(Map<String, Object> options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the matched fields to highlight against this field data. Default to null, meaning just
|
||||
* the named field. If you provide a list of fields here then don't forget to include name as
|
||||
|
@ -625,24 +280,47 @@ public class HighlightBuilder implements ToXContent {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of phrases the fvh will consider.
|
||||
* @param phraseLimit maximum number of phrases the fvh will consider
|
||||
* @return this for chaining
|
||||
*/
|
||||
public Field phraseLimit(Integer phraseLimit) {
|
||||
this.phraseLimit = phraseLimit;
|
||||
return this;
|
||||
public void innerXContent(XContentBuilder builder) throws IOException {
|
||||
builder.startObject(name);
|
||||
// write common options
|
||||
commonOptionsToXContent(builder);
|
||||
// write special field-highlighter options
|
||||
if (fragmentOffset != -1) {
|
||||
builder.field("fragment_offset", fragmentOffset);
|
||||
}
|
||||
if (matchedFields != null) {
|
||||
builder.field("matched_fields", matchedFields);
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Forces the highlighting to highlight this field based on the source even if this field is stored separately.
|
||||
*/
|
||||
public Field forceSource(boolean forceSource) {
|
||||
this.forceSource = forceSource;
|
||||
return this;
|
||||
@Override
|
||||
protected int doHashCode() {
|
||||
return Objects.hash(name, fragmentOffset, Arrays.hashCode(matchedFields));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean doEquals(Field other) {
|
||||
return Objects.equals(name, other.name) &&
|
||||
Objects.equals(fragmentOffset, other.fragmentOffset) &&
|
||||
Arrays.equals(matchedFields, other.matchedFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Field readFrom(StreamInput in) throws IOException {
|
||||
Field field = new Field(in.readString());
|
||||
field.fragmentOffset(in.readVInt());
|
||||
field.matchedFields(in.readOptionalStringArray());
|
||||
field.readOptionsFrom(in);
|
||||
return field;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeString(name);
|
||||
out.writeVInt(fragmentOffset);
|
||||
out.writeOptionalStringArray(matchedFields);
|
||||
writeOptionsTo(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,16 +52,38 @@ import java.util.Set;
|
|||
*/
|
||||
public class HighlighterParseElement implements SearchParseElement {
|
||||
|
||||
private static final String[] DEFAULT_PRE_TAGS = new String[]{"<em>"};
|
||||
private static final String[] DEFAULT_POST_TAGS = new String[]{"</em>"};
|
||||
/** default for whether to highlight fields based on the source even if stored separately */
|
||||
public static final boolean DEFAULT_FORCE_SOURCE = false;
|
||||
/** default for whether a field should be highlighted only if a query matches that field */
|
||||
public static final boolean DEFAULT_REQUIRE_FIELD_MATCH = true;
|
||||
/** default for whether <tt>fvh</tt> should provide highlighting on filter clauses */
|
||||
public static final boolean DEFAULT_HIGHLIGHT_FILTER = false;
|
||||
/** default for highlight fragments being ordered by score */
|
||||
public static final boolean DEFAULT_SCORE_ORDERED = false;
|
||||
/** the default encoder setting */
|
||||
public static final String DEFAULT_ENCODER = "default";
|
||||
/** default for the maximum number of phrases the fvh will consider */
|
||||
public static final int DEFAULT_PHRASE_LIMIT = 256;
|
||||
/** default for fragment size when there are no matches */
|
||||
public static final int DEFAULT_NO_MATCH_SIZE = 0;
|
||||
/** the default number of fragments for highlighting */
|
||||
public static final int DEFAULT_NUMBER_OF_FRAGMENTS = 5;
|
||||
/** the default number of fragments size in characters */
|
||||
public static final int DEFAULT_FRAGMENT_CHAR_SIZE = 100;
|
||||
/** the default opening tag */
|
||||
public static final String[] DEFAULT_PRE_TAGS = new String[]{"<em>"};
|
||||
/** the default closing tag */
|
||||
public static final String[] DEFAULT_POST_TAGS = new String[]{"</em>"};
|
||||
|
||||
private static final String[] STYLED_PRE_TAG = {
|
||||
/** the default opening tags when <tt>tag_schema = "styled"</tt> */
|
||||
public static final String[] STYLED_PRE_TAG = {
|
||||
"<em class=\"hlt1\">", "<em class=\"hlt2\">", "<em class=\"hlt3\">",
|
||||
"<em class=\"hlt4\">", "<em class=\"hlt5\">", "<em class=\"hlt6\">",
|
||||
"<em class=\"hlt7\">", "<em class=\"hlt8\">", "<em class=\"hlt9\">",
|
||||
"<em class=\"hlt10\">"
|
||||
};
|
||||
private static final String[] STYLED_POST_TAGS = {"</em>"};
|
||||
/** the default closing tags when <tt>tag_schema = "styled"</tt> */
|
||||
public static final String[] STYLED_POST_TAGS = {"</em>"};
|
||||
|
||||
@Override
|
||||
public void parse(XContentParser parser, SearchContext context) throws Exception {
|
||||
|
@ -78,11 +100,11 @@ public class HighlighterParseElement implements SearchParseElement {
|
|||
final List<Tuple<String, SearchContextHighlight.FieldOptions.Builder>> fieldsOptions = new ArrayList<>();
|
||||
|
||||
final SearchContextHighlight.FieldOptions.Builder globalOptionsBuilder = new SearchContextHighlight.FieldOptions.Builder()
|
||||
.preTags(DEFAULT_PRE_TAGS).postTags(DEFAULT_POST_TAGS).scoreOrdered(false).highlightFilter(false)
|
||||
.requireFieldMatch(true).forceSource(false).fragmentCharSize(100).numberOfFragments(5)
|
||||
.encoder("default").boundaryMaxScan(SimpleBoundaryScanner.DEFAULT_MAX_SCAN)
|
||||
.preTags(DEFAULT_PRE_TAGS).postTags(DEFAULT_POST_TAGS).scoreOrdered(DEFAULT_SCORE_ORDERED).highlightFilter(DEFAULT_HIGHLIGHT_FILTER)
|
||||
.requireFieldMatch(DEFAULT_REQUIRE_FIELD_MATCH).forceSource(DEFAULT_FORCE_SOURCE).fragmentCharSize(DEFAULT_FRAGMENT_CHAR_SIZE).numberOfFragments(DEFAULT_NUMBER_OF_FRAGMENTS)
|
||||
.encoder(DEFAULT_ENCODER).boundaryMaxScan(SimpleBoundaryScanner.DEFAULT_MAX_SCAN)
|
||||
.boundaryChars(SimpleBoundaryScanner.DEFAULT_BOUNDARY_CHARS)
|
||||
.noMatchSize(0).phraseLimit(256);
|
||||
.noMatchSize(DEFAULT_NO_MATCH_SIZE).phraseLimit(DEFAULT_PHRASE_LIMIT);
|
||||
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* 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.highlight;
|
||||
|
||||
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.index.query.IdsQueryBuilder;
|
||||
import org.elasticsearch.index.query.MatchAllQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.TermQueryBuilder;
|
||||
import org.elasticsearch.search.highlight.HighlightBuilder.Field;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
||||
public class HighlightBuilderTests extends ESTestCase {
|
||||
|
||||
private static final int NUMBER_OF_TESTBUILDERS = 20;
|
||||
private static NamedWriteableRegistry namedWriteableRegistry;
|
||||
|
||||
/**
|
||||
* 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"));
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
namedWriteableRegistry = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test serialization and deserialization of the highlighter builder
|
||||
*/
|
||||
public void testSerialization() throws IOException {
|
||||
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
|
||||
HighlightBuilder original = randomHighlighterBuilder();
|
||||
HighlightBuilder deserialized = serializedCopy(original);
|
||||
assertEquals(deserialized, original);
|
||||
assertEquals(deserialized.hashCode(), original.hashCode());
|
||||
assertNotSame(deserialized, original);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test equality and hashCode properties
|
||||
*/
|
||||
public void testEqualsAndHashcode() throws IOException {
|
||||
for (int runs = 0; runs < NUMBER_OF_TESTBUILDERS; runs++) {
|
||||
HighlightBuilder firstBuilder = randomHighlighterBuilder();
|
||||
assertFalse("highlighter is equal to null", firstBuilder.equals(null));
|
||||
assertFalse("highlighter is equal to incompatible type", firstBuilder.equals(""));
|
||||
assertTrue("highlighter is not equal to self", firstBuilder.equals(firstBuilder));
|
||||
assertThat("same highlighter's hashcode returns different values if called multiple times", firstBuilder.hashCode(),
|
||||
equalTo(firstBuilder.hashCode()));
|
||||
assertThat("different highlighters should not be equal", mutate(firstBuilder), not(equalTo(firstBuilder)));
|
||||
|
||||
HighlightBuilder secondBuilder = serializedCopy(firstBuilder);
|
||||
assertTrue("highlighter is not equal to self", secondBuilder.equals(secondBuilder));
|
||||
assertTrue("highlighter is not equal to its copy", firstBuilder.equals(secondBuilder));
|
||||
assertTrue("equals is not symmetric", secondBuilder.equals(firstBuilder));
|
||||
assertThat("highlighter copy's hashcode is different from original hashcode", secondBuilder.hashCode(), equalTo(firstBuilder.hashCode()));
|
||||
|
||||
HighlightBuilder thirdBuilder = serializedCopy(secondBuilder);
|
||||
assertTrue("highlighter is not equal to self", thirdBuilder.equals(thirdBuilder));
|
||||
assertTrue("highlighter is not equal to its copy", secondBuilder.equals(thirdBuilder));
|
||||
assertThat("highlighter copy's hashcode is different from original hashcode", secondBuilder.hashCode(), equalTo(thirdBuilder.hashCode()));
|
||||
assertTrue("equals is not transitive", firstBuilder.equals(thirdBuilder));
|
||||
assertThat("highlighter copy's hashcode is different from original hashcode", firstBuilder.hashCode(), equalTo(thirdBuilder.hashCode()));
|
||||
assertTrue("equals is not symmetric", thirdBuilder.equals(secondBuilder));
|
||||
assertTrue("equals is not symmetric", thirdBuilder.equals(firstBuilder));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create random shape that is put under test
|
||||
*/
|
||||
private static HighlightBuilder randomHighlighterBuilder() {
|
||||
HighlightBuilder testHighlighter = new HighlightBuilder();
|
||||
setRandomCommonOptions(testHighlighter);
|
||||
testHighlighter.useExplicitFieldOrder(randomBoolean());
|
||||
if (randomBoolean()) {
|
||||
testHighlighter.encoder(randomFrom(Arrays.asList(new String[]{"default", "html"})));
|
||||
}
|
||||
int numberOfFields = randomIntBetween(1,5);
|
||||
for (int i = 0; i < numberOfFields; i++) {
|
||||
Field field = new Field(randomAsciiOfLengthBetween(1, 10));
|
||||
setRandomCommonOptions(field);
|
||||
if (randomBoolean()) {
|
||||
field.fragmentOffset(randomIntBetween(1, 100));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
field.matchedFields(randomStringArray(0, 4));
|
||||
}
|
||||
testHighlighter.field(field);
|
||||
}
|
||||
return testHighlighter;
|
||||
}
|
||||
|
||||
private static void setRandomCommonOptions(AbstractHighlighterBuilder highlightBuilder) {
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.preTags(randomStringArray(0, 3));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.postTags(randomStringArray(0, 3));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.fragmentSize(randomIntBetween(0, 100));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.numOfFragments(randomIntBetween(0, 10));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.highlighterType(randomAsciiOfLengthBetween(1, 10));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.fragmenter(randomAsciiOfLengthBetween(1, 10));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
QueryBuilder highlightQuery;
|
||||
switch (randomInt(2)) {
|
||||
case 0:
|
||||
highlightQuery = new MatchAllQueryBuilder();
|
||||
break;
|
||||
case 1:
|
||||
highlightQuery = new IdsQueryBuilder();
|
||||
break;
|
||||
default:
|
||||
case 2:
|
||||
highlightQuery = new TermQueryBuilder(randomAsciiOfLengthBetween(1, 10), randomAsciiOfLengthBetween(1, 10));
|
||||
break;
|
||||
}
|
||||
highlightQuery.boost((float) randomDoubleBetween(0, 10, false));
|
||||
highlightBuilder.highlightQuery(highlightQuery);
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.order(randomAsciiOfLengthBetween(1, 10));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.highlightFilter(randomBoolean());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.forceSource(randomBoolean());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.boundaryMaxScan(randomIntBetween(0, 10));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.boundaryChars(randomAsciiOfLengthBetween(1, 10).toCharArray());
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.noMatchSize(randomIntBetween(0, 10));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.phraseLimit(randomIntBetween(0, 10));
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
int items = randomIntBetween(0, 5);
|
||||
Map<String, Object> options = new HashMap<String, Object>(items);
|
||||
for (int i = 0; i < items; i++) {
|
||||
Object value = null;
|
||||
switch (randomInt(2)) {
|
||||
case 0:
|
||||
value = randomAsciiOfLengthBetween(1, 10);
|
||||
break;
|
||||
case 1:
|
||||
value = new Integer(randomInt(1000));
|
||||
break;
|
||||
case 2:
|
||||
value = new Boolean(randomBoolean());
|
||||
break;
|
||||
}
|
||||
options.put(randomAsciiOfLengthBetween(1, 10), value);
|
||||
}
|
||||
}
|
||||
if (randomBoolean()) {
|
||||
highlightBuilder.requireFieldMatch(randomBoolean());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void mutateCommonOptions(AbstractHighlighterBuilder highlightBuilder) {
|
||||
switch (randomIntBetween(1, 16)) {
|
||||
case 1:
|
||||
highlightBuilder.preTags(randomStringArray(4, 6));
|
||||
break;
|
||||
case 2:
|
||||
highlightBuilder.postTags(randomStringArray(4, 6));
|
||||
break;
|
||||
case 3:
|
||||
highlightBuilder.fragmentSize(randomIntBetween(101, 200));
|
||||
break;
|
||||
case 4:
|
||||
highlightBuilder.numOfFragments(randomIntBetween(11, 20));
|
||||
break;
|
||||
case 5:
|
||||
highlightBuilder.highlighterType(randomAsciiOfLengthBetween(11, 20));
|
||||
break;
|
||||
case 6:
|
||||
highlightBuilder.fragmenter(randomAsciiOfLengthBetween(11, 20));
|
||||
break;
|
||||
case 7:
|
||||
highlightBuilder.highlightQuery(new TermQueryBuilder(randomAsciiOfLengthBetween(11, 20), randomAsciiOfLengthBetween(11, 20)));
|
||||
break;
|
||||
case 8:
|
||||
highlightBuilder.order(randomAsciiOfLengthBetween(11, 20));
|
||||
break;
|
||||
case 9:
|
||||
highlightBuilder.highlightFilter(toggleOrSet(highlightBuilder.highlightFilter()));
|
||||
case 10:
|
||||
highlightBuilder.forceSource(toggleOrSet(highlightBuilder.forceSource()));
|
||||
break;
|
||||
case 11:
|
||||
highlightBuilder.boundaryMaxScan(randomIntBetween(11, 20));
|
||||
break;
|
||||
case 12:
|
||||
highlightBuilder.boundaryChars(randomAsciiOfLengthBetween(11, 20).toCharArray());
|
||||
break;
|
||||
case 13:
|
||||
highlightBuilder.noMatchSize(randomIntBetween(11, 20));
|
||||
break;
|
||||
case 14:
|
||||
highlightBuilder.phraseLimit(randomIntBetween(11, 20));
|
||||
break;
|
||||
case 15:
|
||||
int items = 6;
|
||||
Map<String, Object> options = new HashMap<String, Object>(items);
|
||||
for (int i = 0; i < items; i++) {
|
||||
options.put(randomAsciiOfLengthBetween(1, 10), randomAsciiOfLengthBetween(1, 10));
|
||||
}
|
||||
highlightBuilder.options(options);
|
||||
break;
|
||||
case 16:
|
||||
highlightBuilder.requireFieldMatch(toggleOrSet(highlightBuilder.requireFieldMatch()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean toggleOrSet(Boolean flag) {
|
||||
if (flag == null) {
|
||||
return randomBoolean();
|
||||
} else {
|
||||
return !flag.booleanValue();
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] randomStringArray(int minSize, int maxSize) {
|
||||
int size = randomIntBetween(minSize, maxSize);
|
||||
String[] randomStrings = new String[size];
|
||||
for (int f = 0; f < size; f++) {
|
||||
randomStrings[f] = randomAsciiOfLengthBetween(1, 10);
|
||||
}
|
||||
return randomStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* mutate the given highlighter builder so the returned one is different in one aspect
|
||||
*/
|
||||
private static HighlightBuilder mutate(HighlightBuilder original) throws IOException {
|
||||
HighlightBuilder mutation = serializedCopy(original);
|
||||
if (randomBoolean()) {
|
||||
mutateCommonOptions(mutation);
|
||||
} else {
|
||||
switch (randomIntBetween(0, 2)) {
|
||||
// change settings that only exists on top level
|
||||
case 0:
|
||||
mutation.useExplicitFieldOrder(!original.useExplicitFieldOrder()); break;
|
||||
case 1:
|
||||
mutation.encoder(original.encoder() + randomAsciiOfLength(2)); break;
|
||||
case 2:
|
||||
if (randomBoolean()) {
|
||||
// add another field
|
||||
mutation.field(new Field(randomAsciiOfLength(10)));
|
||||
} else {
|
||||
// change existing fields
|
||||
List<Field> originalFields = original.fields();
|
||||
Field fieldToChange = originalFields.get(randomInt(originalFields.size() - 1));
|
||||
if (randomBoolean()) {
|
||||
fieldToChange.fragmentOffset(randomIntBetween(101, 200));
|
||||
} else {
|
||||
fieldToChange.matchedFields(randomStringArray(5, 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mutation;
|
||||
}
|
||||
|
||||
private static HighlightBuilder serializedCopy(HighlightBuilder original) throws IOException {
|
||||
try (BytesStreamOutput output = new BytesStreamOutput()) {
|
||||
original.writeTo(output);
|
||||
try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
|
||||
return HighlightBuilder.PROTOTYPE.readFrom(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue