org.elasticsearch.search.internal.InternalSearchHit Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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.internal;
import com.google.common.collect.ImmutableMap;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.compress.CompressorFactory;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentBuilderString;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.SearchShardTarget;
import org.elasticsearch.search.highlight.HighlightField;
import org.elasticsearch.search.internal.InternalSearchHits.StreamContext.ShardTargetType;
import org.elasticsearch.search.lookup.SourceLookup;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.common.lucene.Lucene.readExplanation;
import static org.elasticsearch.common.lucene.Lucene.writeExplanation;
import static org.elasticsearch.search.SearchShardTarget.readSearchShardTarget;
import static org.elasticsearch.search.highlight.HighlightField.readHighlightField;
import static org.elasticsearch.search.internal.InternalSearchHitField.readSearchHitField;
/**
*
*/
public class InternalSearchHit implements SearchHit {
private static final Object[] EMPTY_SORT_VALUES = new Object[0];
private transient int docId;
private float score = Float.NEGATIVE_INFINITY;
private Text id;
private Text type;
private InternalNestedIdentity nestedIdentity;
private long version = -1;
private BytesReference source;
private Map fields = ImmutableMap.of();
private Map highlightFields = null;
private Object[] sortValues = EMPTY_SORT_VALUES;
private String[] matchedQueries = Strings.EMPTY_ARRAY;
private Explanation explanation;
@Nullable
private SearchShardTarget shard;
private Map sourceAsMap;
private byte[] sourceAsBytes;
private Map innerHits;
private InternalSearchHit() {
}
public InternalSearchHit(int docId, String id, Text type, Map fields) {
this.docId = docId;
this.id = new Text(id);
this.type = type;
this.fields = fields;
}
public InternalSearchHit(int nestedTopDocId, String id, Text type, InternalNestedIdentity nestedIdentity, Map fields) {
this.docId = nestedTopDocId;
this.id = new Text(id);
this.type = type;
this.nestedIdentity = nestedIdentity;
this.fields = fields;
}
public int docId() {
return this.docId;
}
public void shardTarget(SearchShardTarget shardTarget) {
this.shard = shardTarget;
if (innerHits != null) {
for (InternalSearchHits searchHits : innerHits.values()) {
searchHits.shardTarget(shardTarget);
}
}
}
public void score(float score) {
this.score = score;
}
@Override
public float score() {
return this.score;
}
@Override
public float getScore() {
return score();
}
public void version(long version) {
this.version = version;
}
@Override
public long version() {
return this.version;
}
@Override
public long getVersion() {
return this.version;
}
@Override
public String index() {
return shard.index();
}
@Override
public String getIndex() {
return index();
}
@Override
public String id() {
return id.string();
}
@Override
public String getId() {
return id();
}
@Override
public String type() {
return type.string();
}
@Override
public String getType() {
return type();
}
@Override
public NestedIdentity getNestedIdentity() {
return nestedIdentity;
}
/**
* Returns bytes reference, also un compress the source if needed.
*/
@Override
public BytesReference sourceRef() {
if (this.source == null) {
return null;
}
try {
this.source = CompressorFactory.uncompressIfNeeded(this.source);
return this.source;
} catch (IOException e) {
throw new ElasticsearchParseException("failed to decompress source", e);
}
}
/**
* Sets representation, might be compressed....
*/
public InternalSearchHit sourceRef(BytesReference source) {
this.source = source;
this.sourceAsBytes = null;
this.sourceAsMap = null;
return this;
}
@Override
public BytesReference getSourceRef() {
return sourceRef();
}
/**
* Internal source representation, might be compressed....
*/
public BytesReference internalSourceRef() {
return source;
}
@Override
public byte[] source() {
if (source == null) {
return null;
}
if (sourceAsBytes != null) {
return sourceAsBytes;
}
this.sourceAsBytes = sourceRef().toBytes();
return this.sourceAsBytes;
}
@Override
public boolean isSourceEmpty() {
return source == null;
}
@Override
public Map getSource() {
return sourceAsMap();
}
@Override
public String sourceAsString() {
if (source == null) {
return null;
}
try {
return XContentHelper.convertToJson(sourceRef(), false);
} catch (IOException e) {
throw new ElasticsearchParseException("failed to convert source to a json string");
}
}
@Override
public String getSourceAsString() {
return sourceAsString();
}
@SuppressWarnings({"unchecked"})
@Override
public Map sourceAsMap() throws ElasticsearchParseException {
if (source == null) {
return null;
}
if (sourceAsMap != null) {
return sourceAsMap;
}
sourceAsMap = SourceLookup.sourceAsMap(source);
return sourceAsMap;
}
@Override
public Iterator iterator() {
return fields.values().iterator();
}
@Override
public SearchHitField field(String fieldName) {
return fields().get(fieldName);
}
@Override
public Map fields() {
if (fields == null) {
return ImmutableMap.of();
}
return fields;
}
// returns the fields without handling null cases
public Map fieldsOrNull() {
return this.fields;
}
@Override
public Map getFields() {
return fields();
}
public void fields(Map fields) {
this.fields = fields;
}
public Map internalHighlightFields() {
return highlightFields;
}
@Override
public Map highlightFields() {
if (highlightFields == null) {
return ImmutableMap.of();
}
return this.highlightFields;
}
@Override
public Map getHighlightFields() {
return highlightFields();
}
public void highlightFields(Map highlightFields) {
this.highlightFields = highlightFields;
}
public void sortValues(Object[] sortValues) {
// LUCENE 4 UPGRADE: There must be a better way
// we want to convert to a Text object here, and not BytesRef
// Don't write into sortValues! Otherwise the fields in FieldDoc is modified, which may be used in other places. (SearchContext#lastEmitedDoc)
Object[] sortValuesCopy = new Object[sortValues.length];
System.arraycopy(sortValues, 0, sortValuesCopy, 0, sortValues.length);
if (sortValues != null) {
for (int i = 0; i < sortValues.length; i++) {
if (sortValues[i] instanceof BytesRef) {
sortValuesCopy[i] = new Text(new BytesArray((BytesRef) sortValues[i]));
}
}
}
this.sortValues = sortValuesCopy;
}
@Override
public Object[] sortValues() {
return sortValues;
}
@Override
public Object[] getSortValues() {
return sortValues();
}
@Override
public Explanation explanation() {
return explanation;
}
@Override
public Explanation getExplanation() {
return explanation();
}
public void explanation(Explanation explanation) {
this.explanation = explanation;
}
@Override
public SearchShardTarget shard() {
return shard;
}
@Override
public SearchShardTarget getShard() {
return shard();
}
public void shard(SearchShardTarget target) {
this.shard = target;
}
public void matchedQueries(String[] matchedQueries) {
this.matchedQueries = matchedQueries;
}
@Override
public String[] matchedQueries() {
return this.matchedQueries;
}
@Override
public String[] getMatchedQueries() {
return this.matchedQueries;
}
@Override
@SuppressWarnings("unchecked")
public Map getInnerHits() {
return (Map) innerHits;
}
public void setInnerHits(Map innerHits) {
this.innerHits = innerHits;
}
public static class Fields {
static final XContentBuilderString _INDEX = new XContentBuilderString("_index");
static final XContentBuilderString _TYPE = new XContentBuilderString("_type");
static final XContentBuilderString _ID = new XContentBuilderString("_id");
static final XContentBuilderString _VERSION = new XContentBuilderString("_version");
static final XContentBuilderString _SCORE = new XContentBuilderString("_score");
static final XContentBuilderString FIELDS = new XContentBuilderString("fields");
static final XContentBuilderString HIGHLIGHT = new XContentBuilderString("highlight");
static final XContentBuilderString SORT = new XContentBuilderString("sort");
static final XContentBuilderString MATCHED_QUERIES = new XContentBuilderString("matched_queries");
static final XContentBuilderString _EXPLANATION = new XContentBuilderString("_explanation");
static final XContentBuilderString VALUE = new XContentBuilderString("value");
static final XContentBuilderString DESCRIPTION = new XContentBuilderString("description");
static final XContentBuilderString DETAILS = new XContentBuilderString("details");
static final XContentBuilderString INNER_HITS = new XContentBuilderString("inner_hits");
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
List metaFields = new ArrayList<>();
List otherFields = new ArrayList<>();
if (fields != null && !fields.isEmpty()) {
for (SearchHitField field : fields.values()) {
if (field.values().isEmpty()) {
continue;
}
if (field.isMetadataField()) {
metaFields.add(field);
} else {
otherFields.add(field);
}
}
}
builder.startObject();
// For inner_hit hits shard is null and that is ok, because the parent search hit has all this information.
// Even if this was included in the inner_hit hits this would be the same, so better leave it out.
if (explanation() != null && shard != null) {
builder.field("_shard", shard.shardId());
builder.field("_node", shard.nodeIdText());
}
if (shard != null) {
builder.field(Fields._INDEX, shard.indexText());
}
builder.field(Fields._TYPE, type);
builder.field(Fields._ID, id);
if (nestedIdentity != null) {
nestedIdentity.toXContent(builder, params);
}
if (version != -1) {
builder.field(Fields._VERSION, version);
}
if (Float.isNaN(score)) {
builder.nullField(Fields._SCORE);
} else {
builder.field(Fields._SCORE, score);
}
for (SearchHitField field : metaFields) {
builder.field(field.name(), field.value());
}
if (source != null) {
XContentHelper.writeRawField("_source", source, builder, params);
}
if (!otherFields.isEmpty()) {
builder.startObject(Fields.FIELDS);
for (SearchHitField field : otherFields) {
builder.startArray(field.name());
for (Object value : field.getValues()) {
builder.value(value);
}
builder.endArray();
}
builder.endObject();
}
if (highlightFields != null && !highlightFields.isEmpty()) {
builder.startObject(Fields.HIGHLIGHT);
for (HighlightField field : highlightFields.values()) {
builder.field(field.name());
if (field.fragments() == null) {
builder.nullValue();
} else {
builder.startArray();
for (Text fragment : field.fragments()) {
builder.value(fragment);
}
builder.endArray();
}
}
builder.endObject();
}
if (sortValues != null && sortValues.length > 0) {
builder.startArray(Fields.SORT);
for (Object sortValue : sortValues) {
builder.value(sortValue);
}
builder.endArray();
}
if (matchedQueries.length > 0) {
builder.startArray(Fields.MATCHED_QUERIES);
for (String matchedFilter : matchedQueries) {
builder.value(matchedFilter);
}
builder.endArray();
}
if (explanation() != null) {
builder.field(Fields._EXPLANATION);
buildExplanation(builder, explanation());
}
if (innerHits != null) {
builder.startObject(Fields.INNER_HITS);
for (Map.Entry entry : innerHits.entrySet()) {
builder.startObject(entry.getKey());
entry.getValue().toXContent(builder, params);
builder.endObject();
}
builder.endObject();
}
builder.endObject();
return builder;
}
private void buildExplanation(XContentBuilder builder, Explanation explanation) throws IOException {
builder.startObject();
builder.field(Fields.VALUE, explanation.getValue());
builder.field(Fields.DESCRIPTION, explanation.getDescription());
Explanation[] innerExps = explanation.getDetails();
if (innerExps != null) {
builder.startArray(Fields.DETAILS);
for (Explanation exp : innerExps) {
buildExplanation(builder, exp);
}
builder.endArray();
}
builder.endObject();
}
public static InternalSearchHit readSearchHit(StreamInput in, InternalSearchHits.StreamContext context) throws IOException {
InternalSearchHit hit = new InternalSearchHit();
hit.readFrom(in, context);
return hit;
}
@Override
public void readFrom(StreamInput in) throws IOException {
readFrom(in, InternalSearchHits.streamContext().streamShardTarget(ShardTargetType.STREAM));
}
public void readFrom(StreamInput in, InternalSearchHits.StreamContext context) throws IOException {
score = in.readFloat();
id = in.readText();
type = in.readText();
nestedIdentity = in.readOptionalStreamable(new InternalNestedIdentity());
version = in.readLong();
source = in.readBytesReference();
if (source.length() == 0) {
source = null;
}
if (in.readBoolean()) {
explanation = readExplanation(in);
}
int size = in.readVInt();
if (size == 0) {
fields = ImmutableMap.of();
} else if (size == 1) {
SearchHitField hitField = readSearchHitField(in);
fields = ImmutableMap.of(hitField.name(), hitField);
} else if (size == 2) {
SearchHitField hitField1 = readSearchHitField(in);
SearchHitField hitField2 = readSearchHitField(in);
fields = ImmutableMap.of(hitField1.name(), hitField1, hitField2.name(), hitField2);
} else if (size == 3) {
SearchHitField hitField1 = readSearchHitField(in);
SearchHitField hitField2 = readSearchHitField(in);
SearchHitField hitField3 = readSearchHitField(in);
fields = ImmutableMap.of(hitField1.name(), hitField1, hitField2.name(), hitField2, hitField3.name(), hitField3);
} else if (size == 4) {
SearchHitField hitField1 = readSearchHitField(in);
SearchHitField hitField2 = readSearchHitField(in);
SearchHitField hitField3 = readSearchHitField(in);
SearchHitField hitField4 = readSearchHitField(in);
fields = ImmutableMap.of(hitField1.name(), hitField1, hitField2.name(), hitField2, hitField3.name(), hitField3, hitField4.name(), hitField4);
} else if (size == 5) {
SearchHitField hitField1 = readSearchHitField(in);
SearchHitField hitField2 = readSearchHitField(in);
SearchHitField hitField3 = readSearchHitField(in);
SearchHitField hitField4 = readSearchHitField(in);
SearchHitField hitField5 = readSearchHitField(in);
fields = ImmutableMap.of(hitField1.name(), hitField1, hitField2.name(), hitField2, hitField3.name(), hitField3, hitField4.name(), hitField4, hitField5.name(), hitField5);
} else {
ImmutableMap.Builder builder = ImmutableMap.builder();
for (int i = 0; i < size; i++) {
SearchHitField hitField = readSearchHitField(in);
builder.put(hitField.name(), hitField);
}
fields = builder.build();
}
size = in.readVInt();
if (size == 0) {
highlightFields = ImmutableMap.of();
} else if (size == 1) {
HighlightField field = readHighlightField(in);
highlightFields = ImmutableMap.of(field.name(), field);
} else if (size == 2) {
HighlightField field1 = readHighlightField(in);
HighlightField field2 = readHighlightField(in);
highlightFields = ImmutableMap.of(field1.name(), field1, field2.name(), field2);
} else if (size == 3) {
HighlightField field1 = readHighlightField(in);
HighlightField field2 = readHighlightField(in);
HighlightField field3 = readHighlightField(in);
highlightFields = ImmutableMap.of(field1.name(), field1, field2.name(), field2, field3.name(), field3);
} else if (size == 4) {
HighlightField field1 = readHighlightField(in);
HighlightField field2 = readHighlightField(in);
HighlightField field3 = readHighlightField(in);
HighlightField field4 = readHighlightField(in);
highlightFields = ImmutableMap.of(field1.name(), field1, field2.name(), field2, field3.name(), field3, field4.name(), field4);
} else {
ImmutableMap.Builder builder = ImmutableMap.builder();
for (int i = 0; i < size; i++) {
HighlightField field = readHighlightField(in);
builder.put(field.name(), field);
}
highlightFields = builder.build();
}
size = in.readVInt();
if (size > 0) {
sortValues = new Object[size];
for (int i = 0; i < sortValues.length; i++) {
byte type = in.readByte();
if (type == 0) {
sortValues[i] = null;
} else if (type == 1) {
sortValues[i] = in.readString();
} else if (type == 2) {
sortValues[i] = in.readInt();
} else if (type == 3) {
sortValues[i] = in.readLong();
} else if (type == 4) {
sortValues[i] = in.readFloat();
} else if (type == 5) {
sortValues[i] = in.readDouble();
} else if (type == 6) {
sortValues[i] = in.readByte();
} else if (type == 7) {
sortValues[i] = in.readShort();
} else if (type == 8) {
sortValues[i] = in.readBoolean();
} else if (type == 9) {
sortValues[i] = in.readText();
} else {
throw new IOException("Can't match type [" + type + "]");
}
}
}
size = in.readVInt();
if (size > 0) {
matchedQueries = new String[size];
for (int i = 0; i < size; i++) {
matchedQueries[i] = in.readString();
}
}
if (context.streamShardTarget() == ShardTargetType.STREAM) {
if (in.readBoolean()) {
shard = readSearchShardTarget(in);
}
} else if (context.streamShardTarget() == ShardTargetType.LOOKUP) {
int lookupId = in.readVInt();
if (lookupId > 0) {
shard = context.handleShardLookup().get(lookupId);
}
}
size = in.readVInt();
if (size > 0) {
innerHits = new HashMap<>(size);
for (int i = 0; i < size; i++) {
String key = in.readString();
ShardTargetType shardTarget = context.streamShardTarget();
InternalSearchHits value = InternalSearchHits.readSearchHits(in, context.streamShardTarget(ShardTargetType.NO_STREAM));
context.streamShardTarget(shardTarget);
innerHits.put(key, value);
}
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
writeTo(out, InternalSearchHits.streamContext().streamShardTarget(ShardTargetType.STREAM));
}
public void writeTo(StreamOutput out, InternalSearchHits.StreamContext context) throws IOException {
out.writeFloat(score);
out.writeText(id);
out.writeText(type);
out.writeOptionalStreamable(nestedIdentity);
out.writeLong(version);
out.writeBytesReference(source);
if (explanation == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
writeExplanation(out, explanation);
}
if (fields == null) {
out.writeVInt(0);
} else {
out.writeVInt(fields.size());
for (SearchHitField hitField : fields().values()) {
hitField.writeTo(out);
}
}
if (highlightFields == null) {
out.writeVInt(0);
} else {
out.writeVInt(highlightFields.size());
for (HighlightField highlightField : highlightFields.values()) {
highlightField.writeTo(out);
}
}
if (sortValues.length == 0) {
out.writeVInt(0);
} else {
out.writeVInt(sortValues.length);
for (Object sortValue : sortValues) {
if (sortValue == null) {
out.writeByte((byte) 0);
} else {
Class type = sortValue.getClass();
if (type == String.class) {
out.writeByte((byte) 1);
out.writeString((String) sortValue);
} else if (type == Integer.class) {
out.writeByte((byte) 2);
out.writeInt((Integer) sortValue);
} else if (type == Long.class) {
out.writeByte((byte) 3);
out.writeLong((Long) sortValue);
} else if (type == Float.class) {
out.writeByte((byte) 4);
out.writeFloat((Float) sortValue);
} else if (type == Double.class) {
out.writeByte((byte) 5);
out.writeDouble((Double) sortValue);
} else if (type == Byte.class) {
out.writeByte((byte) 6);
out.writeByte((Byte) sortValue);
} else if (type == Short.class) {
out.writeByte((byte) 7);
out.writeShort((Short) sortValue);
} else if (type == Boolean.class) {
out.writeByte((byte) 8);
out.writeBoolean((Boolean) sortValue);
} else if (sortValue instanceof Text) {
out.writeByte((byte) 9);
out.writeText((Text) sortValue);
} else {
throw new IOException("Can't handle sort field value of type [" + type + "]");
}
}
}
}
if (matchedQueries.length == 0) {
out.writeVInt(0);
} else {
out.writeVInt(matchedQueries.length);
for (String matchedFilter : matchedQueries) {
out.writeString(matchedFilter);
}
}
if (context.streamShardTarget() == ShardTargetType.STREAM) {
if (shard == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
shard.writeTo(out);
}
} else if (context.streamShardTarget() == ShardTargetType.LOOKUP) {
if (shard == null) {
out.writeVInt(0);
} else {
out.writeVInt(context.shardHandleLookup().get(shard));
}
}
if (innerHits == null) {
out.writeVInt(0);
} else {
out.writeVInt(innerHits.size());
for (Map.Entry entry : innerHits.entrySet()) {
out.writeString(entry.getKey());
ShardTargetType shardTarget = context.streamShardTarget();
entry.getValue().writeTo(out, context.streamShardTarget(ShardTargetType.NO_STREAM));
context.streamShardTarget(shardTarget);
}
}
}
public final static class InternalNestedIdentity implements NestedIdentity, Streamable, ToXContent {
private Text field;
private int offset;
private InternalNestedIdentity child;
public InternalNestedIdentity(String field, int offset, InternalNestedIdentity child) {
this.field = new Text(field);
this.offset = offset;
this.child = child;
}
InternalNestedIdentity() {
}
@Override
public Text getField() {
return field;
}
@Override
public int getOffset() {
return offset;
}
@Override
public NestedIdentity getChild() {
return child;
}
@Override
public void readFrom(StreamInput in) throws IOException {
field = in.readOptionalText();
offset = in.readInt();
child = in.readOptionalStreamable(new InternalNestedIdentity());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalText(field);
out.writeInt(offset);
out.writeOptionalStreamable(child);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(Fields._NESTED);
if (field != null) {
builder.field(Fields._NESTED_FIELD, field);
}
if (offset != -1) {
builder.field(Fields._NESTED_OFFSET, offset);
}
if (child != null) {
builder = child.toXContent(builder, params);
}
builder.endObject();
return builder;
}
public static class Fields {
static final XContentBuilderString _NESTED = new XContentBuilderString("_nested");
static final XContentBuilderString _NESTED_FIELD = new XContentBuilderString("field");
static final XContentBuilderString _NESTED_OFFSET = new XContentBuilderString("offset");
}
}
}