Add fromXContent to InternalNestedIdentity

This adds a fromXContent method and unit test to InternalNestedIdentity so we can parse it as part of a search response. This is part of the preparation for parsing search responses on the client side.
This commit is contained in:
Christoph Büscher 2016-12-09 14:52:06 +01:00 committed by GitHub
parent db0660a7ea
commit 2592ff86ce
2 changed files with 201 additions and 19 deletions

View File

@ -23,16 +23,21 @@ 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.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParseFieldMatcherSupplier;
import org.elasticsearch.common.Strings;
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.io.stream.Writeable;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
@ -48,12 +53,15 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.Collections.unmodifiableMap;
import static org.elasticsearch.common.lucene.Lucene.readExplanation;
import static org.elasticsearch.common.lucene.Lucene.writeExplanation;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
import static org.elasticsearch.search.fetch.subphase.highlight.HighlightField.readHighlightField;
import static org.elasticsearch.search.internal.InternalSearchHitField.readSearchHitField;
@ -274,7 +282,6 @@ public class InternalSearchHit implements SearchHit {
return sourceAsString();
}
@SuppressWarnings({"unchecked"})
@Override
public Map<String, Object> sourceAsMap() throws ElasticsearchParseException {
if (source == null) {
@ -468,8 +475,8 @@ public class InternalSearchHit implements SearchHit {
builder.field(Fields._SCORE, score);
}
for (SearchHitField field : metaFields) {
Object value = (Object) field.value();
builder.field(field.name(), value);
Object value = field.value();
builder.field(field.name(), value);
}
if (source != null) {
XContentHelper.writeRawField("_source", source, builder, params);
@ -557,7 +564,7 @@ public class InternalSearchHit implements SearchHit {
score = in.readFloat();
id = in.readOptionalText();
type = in.readOptionalText();
nestedIdentity = in.readOptionalStreamable(InternalNestedIdentity::new);
nestedIdentity = in.readOptionalWriteable(InternalNestedIdentity::new);
version = in.readLong();
source = in.readBytesReference();
if (source.length() == 0) {
@ -649,7 +656,7 @@ public class InternalSearchHit implements SearchHit {
out.writeFloat(score);
out.writeOptionalText(id);
out.writeOptionalText(type);
out.writeOptionalStreamable(nestedIdentity);
out.writeOptionalWriteable(nestedIdentity);
out.writeLong(version);
out.writeBytesReference(source);
if (explanation == null) {
@ -735,7 +742,7 @@ public class InternalSearchHit implements SearchHit {
}
}
public static final class InternalNestedIdentity implements NestedIdentity, Streamable, ToXContent {
public static final class InternalNestedIdentity implements NestedIdentity, Writeable, ToXContent {
private Text field;
private int offset;
@ -747,7 +754,10 @@ public class InternalSearchHit implements SearchHit {
this.child = child;
}
InternalNestedIdentity() {
InternalNestedIdentity(StreamInput in) throws IOException {
field = in.readOptionalText();
offset = in.readInt();
child = in.readOptionalWriteable(InternalNestedIdentity::new);
}
@Override
@ -765,23 +775,25 @@ public class InternalSearchHit implements SearchHit {
return child;
}
@Override
public void readFrom(StreamInput in) throws IOException {
field = in.readOptionalText();
offset = in.readInt();
child = in.readOptionalStreamable(InternalNestedIdentity::new);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalText(field);
out.writeInt(offset);
out.writeOptionalStreamable(child);
out.writeOptionalWriteable(child);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(Fields._NESTED);
builder.field(Fields._NESTED);
return innerToXContent(builder, params);
}
/**
* Rendering of the inner XContent object without the leading field name. This way the structure innerToXContent renders and
* fromXContent parses correspond to each other.
*/
XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (field != null) {
builder.field(Fields._NESTED_FIELD, field);
}
@ -795,12 +807,42 @@ public class InternalSearchHit implements SearchHit {
return builder;
}
public static class Fields {
private static final ConstructingObjectParser<InternalNestedIdentity, ParseFieldMatcherSupplier> PARSER = new ConstructingObjectParser<>(
"nested_identity",
ctorArgs -> new InternalNestedIdentity((String) ctorArgs[0], (int) ctorArgs[1], (InternalNestedIdentity) ctorArgs[2]));
static {
PARSER.declareString(constructorArg(), new ParseField(Fields._NESTED_FIELD));
PARSER.declareInt(constructorArg(), new ParseField(Fields._NESTED_OFFSET));
PARSER.declareObject(optionalConstructorArg(), PARSER, new ParseField(Fields._NESTED));
}
public static InternalNestedIdentity fromXContent(XContentParser parser) {
return PARSER.apply(parser, () -> ParseFieldMatcher.EMPTY);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
InternalNestedIdentity other = (InternalNestedIdentity) obj;
return Objects.equals(field, other.field) &&
Objects.equals(offset, other.offset) &&
Objects.equals(child, other.child);
}
@Override
public int hashCode() {
return Objects.hash(field, offset, child);
}
public static class Fields {
static final String _NESTED = "_nested";
static final String _NESTED_FIELD = "field";
static final String _NESTED_OFFSET = "offset";
}
}

View File

@ -0,0 +1,140 @@
/*
* 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 org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
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.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.search.internal.InternalSearchHit.InternalNestedIdentity;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
public class InternalNestedIdentityTests extends ESTestCase {
public static InternalNestedIdentity createTestItem(int depth) {
String field = frequently() ? randomAsciiOfLengthBetween(1, 20) : randomRealisticUnicodeOfCodepointLengthBetween(1, 20);
int offset = randomInt(10);
InternalNestedIdentity child = null;
if (depth > 0) {
child = createTestItem(depth - 1);
}
return new InternalNestedIdentity(field, offset, child);
}
public void testFromXContent() throws IOException {
InternalNestedIdentity nestedIdentity = createTestItem(randomInt(3));
XContentType xcontentType = randomFrom(XContentType.values());
XContentBuilder builder = XContentFactory.contentBuilder(xcontentType);
if (randomBoolean()) {
builder.prettyPrint();
}
builder = nestedIdentity.innerToXContent(builder, ToXContent.EMPTY_PARAMS);
XContentParser parser = xcontentType.xContent().createParser(builder.bytes());
InternalNestedIdentity parsedNestedIdentity = InternalNestedIdentity.fromXContent(parser);
assertEquals(nestedIdentity, parsedNestedIdentity);
assertNull(parser.nextToken());
}
public void testToXContent() throws IOException {
InternalNestedIdentity nestedIdentity = new InternalNestedIdentity("foo", 5, null);
XContentBuilder builder = JsonXContent.contentBuilder();
builder.prettyPrint();
builder.startObject();
nestedIdentity.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
assertEquals(
"{\n" +
" \"_nested\" : {\n" +
" \"field\" : \"foo\",\n" +
" \"offset\" : 5\n" +
" }\n" +
"}", builder.string());
nestedIdentity = new InternalNestedIdentity("foo", 5, new InternalNestedIdentity("bar", 3, null));
builder = JsonXContent.contentBuilder();
builder.prettyPrint();
builder.startObject();
nestedIdentity.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
assertEquals(
"{\n" +
" \"_nested\" : {\n" +
" \"field\" : \"foo\",\n" +
" \"offset\" : 5,\n" +
" \"_nested\" : {\n" +
" \"field\" : \"bar\",\n" +
" \"offset\" : 3\n" +
" }\n" +
" }\n" +
"}", builder.string());
}
/**
* Test equality and hashCode properties
*/
public void testEqualsAndHashcode() {
checkEqualsAndHashCode(createTestItem(randomInt(3)), InternalNestedIdentityTests::copy, InternalNestedIdentityTests::mutate);
}
public void testSerialization() throws IOException {
InternalNestedIdentity nestedIdentity = createTestItem(randomInt(3));
try (BytesStreamOutput output = new BytesStreamOutput()) {
nestedIdentity.writeTo(output);
try (StreamInput in = output.bytes().streamInput()) {
InternalNestedIdentity deserializedCopy = new InternalNestedIdentity(in);
assertEquals(nestedIdentity, deserializedCopy);
assertEquals(nestedIdentity.hashCode(), deserializedCopy.hashCode());
assertNotSame(nestedIdentity, deserializedCopy);
}
}
}
private static InternalNestedIdentity mutate(InternalNestedIdentity original) {
if (original == null) {
return createTestItem(0);
}
List<Supplier<InternalNestedIdentity>> mutations = new ArrayList<>();
int offset = original.getOffset();
InternalNestedIdentity child = (InternalNestedIdentity) original.getChild();
String fieldName = original.getField().string();
mutations.add(() ->
new InternalNestedIdentity(original.getField().string() + "_prefix", offset, child));
mutations.add(() -> new InternalNestedIdentity(fieldName, offset + 1, child));
mutations.add(() -> new InternalNestedIdentity(fieldName, offset, mutate(child)));
return randomFrom(mutations).get();
}
private static InternalNestedIdentity copy(InternalNestedIdentity original) {
InternalNestedIdentity child = (InternalNestedIdentity) original.getChild();
return new InternalNestedIdentity(original.getField().string(), original.getOffset(), child != null ? copy(child) : null);
}
}