Add parsing methods for UpdateResponse (#22586)

This commit adds the fromXContent() method to the UpdateResponse class, so that it can be used with the high level rest client.
This commit is contained in:
Tanguy Leroux 2017-01-19 12:49:45 +01:00 committed by GitHub
parent 21dae1924f
commit 833284cae2
7 changed files with 282 additions and 18 deletions

View File

@ -292,8 +292,8 @@ public abstract class DocWriteResponse extends ReplicationResponse implements Wr
objParser.declareString(constructorArg(), new ParseField(_ID));
objParser.declareLong(constructorArg(), new ParseField(_VERSION));
objParser.declareString(constructorArg(), new ParseField(RESULT));
objParser.declareObject(optionalConstructorArg(), (p, c) -> ShardInfo.fromXContent(p), new ParseField(_SHARDS));
objParser.declareLong(optionalConstructorArg(), new ParseField(_SEQ_NO));
objParser.declareBoolean(DocWriteResponse::setForcedRefresh, new ParseField(FORCED_REFRESH));
objParser.declareObject(DocWriteResponse::setShardInfo, (p, c) -> ShardInfo.fromXContent(p), new ParseField(_SHARDS));
}
}

View File

@ -93,9 +93,13 @@ public class IndexResponse extends DocWriteResponse {
String type = (String) args[1];
String id = (String) args[2];
long version = (long) args[3];
long seqNo = (args[5] != null) ? (long) args[5] : SequenceNumbersService.UNASSIGNED_SEQ_NO;
boolean created = (boolean) args[6];
return new IndexResponse(shardId, type, id, seqNo, version, created);
ShardInfo shardInfo = (ShardInfo) args[5];
long seqNo = (args[6] != null) ? (long) args[6] : SequenceNumbersService.UNASSIGNED_SEQ_NO;
boolean created = (boolean) args[7];
IndexResponse indexResponse = new IndexResponse(shardId, type, id, seqNo, version, created);
indexResponse.setShardInfo(shardInfo);
return indexResponse;
});
DocWriteResponse.declareParserFields(PARSER);
PARSER.declareBoolean(constructorArg(), new ParseField(CREATED));

View File

@ -20,18 +20,26 @@
package org.elasticsearch.action.update;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.seqno.SequenceNumbersService;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.rest.RestStatus;
import java.io.IOException;
import java.util.function.BiConsumer;
public class UpdateResponse extends DocWriteResponse {
private static final String GET = "get";
private GetResult getResult;
public UpdateResponse() {
@ -82,15 +90,11 @@ public class UpdateResponse extends DocWriteResponse {
}
}
static final class Fields {
static final String GET = "get";
}
@Override
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
super.innerToXContent(builder, params);
if (getGetResult() != null) {
builder.startObject(Fields.GET);
builder.startObject(GET);
getGetResult().toXContentEmbedded(builder, params);
builder.endObject();
}
@ -109,4 +113,45 @@ public class UpdateResponse extends DocWriteResponse {
builder.append(",shards=").append(getShardInfo());
return builder.append("]").toString();
}
private static final ConstructingObjectParser<UpdateResponse, Void> PARSER;
static {
PARSER = new ConstructingObjectParser<>(UpdateResponse.class.getName(),
args -> {
// index uuid and shard id are unknown and can't be parsed back for now.
String index = (String) args[0];
ShardId shardId = new ShardId(new Index(index, IndexMetaData.INDEX_UUID_NA_VALUE), -1);
String type = (String) args[1];
String id = (String) args[2];
long version = (long) args[3];
ShardInfo shardInfo = (ShardInfo) args[5];
Long seqNo = (Long) args[6];
Result result = null;
for (Result r : Result.values()) {
if (r.getLowercase().equals(args[4])) {
result = r;
break;
}
}
UpdateResponse updateResponse = null;
if (shardInfo != null && seqNo != null) {
updateResponse = new UpdateResponse(shardInfo, shardId, type, id, seqNo, version, result);
} else {
updateResponse = new UpdateResponse(shardId, type, id, version, result);
}
return updateResponse;
});
DocWriteResponse.declareParserFields(PARSER);
BiConsumer<UpdateResponse, GetResult> setGetResult = (update, get) ->
update.setGetResult(new GetResult(update.getIndex(), update.getType(), update.getId(), update.getVersion(),
get.isExists(), get.internalSourceRef(), get.getFields()));
PARSER.declareObject(setGetResult, (parser, context) -> GetResult.fromXContentEmbedded(parser), new ParseField(GET));
}
public static UpdateResponse fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
}

View File

@ -266,10 +266,11 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContentObje
return builder;
}
public static GetResult fromXContent(XContentParser parser) throws IOException {
public static GetResult fromXContentEmbedded(XContentParser parser) throws IOException {
XContentParser.Token token = parser.nextToken();
ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
String currentFieldName = null;
ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
String currentFieldName = parser.currentName();
String index = null, type = null, id = null;
long version = -1;
boolean found = false;
@ -313,6 +314,13 @@ public class GetResult implements Streamable, Iterable<GetField>, ToXContentObje
return new GetResult(index, type, id, version, found, source, fields);
}
public static GetResult fromXContent(XContentParser parser) throws IOException {
XContentParser.Token token = parser.nextToken();
ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
return fromXContentEmbedded(parser);
}
public static GetResult readGetResult(StreamInput in) throws IOException {
GetResult result = new GetResult();
result.readFrom(in);

View File

@ -20,6 +20,7 @@
package org.elasticsearch.action.index;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
@ -81,11 +82,11 @@ public class IndexResponseTests extends ESTestCase {
}
}
private static void assertIndexResponse(IndexResponse expected, Map<String, Object> actual) {
public static void assertDocWriteResponse(DocWriteResponse expected, Map<String, Object> actual) {
assertEquals(expected.getIndex(), actual.get("_index"));
assertEquals(expected.getType(), actual.get("_type"));
assertEquals(expected.getId(), actual.get("_id"));
assertEquals(expected.getVersion(), ((Integer) actual.get("_version")).longValue());
assertEquals(expected.getVersion(), ((Number) actual.get("_version")).longValue());
assertEquals(expected.getResult().getLowercase(), actual.get("result"));
if (expected.forcedRefresh()) {
assertTrue((Boolean) actual.get("forced_refresh"));
@ -93,7 +94,7 @@ public class IndexResponseTests extends ESTestCase {
assertFalse(actual.containsKey("forced_refresh"));
}
if (expected.getSeqNo() >= 0) {
assertEquals(expected.getSeqNo(), ((Integer) actual.get("_seq_no")).longValue());
assertEquals(expected.getSeqNo(), ((Number) actual.get("_seq_no")).longValue());
} else {
assertFalse(actual.containsKey("_seq_no"));
}
@ -151,6 +152,15 @@ public class IndexResponseTests extends ESTestCase {
}
}
private static void assertIndexResponse(IndexResponse expected, Map<String, Object> actual) {
assertDocWriteResponse(expected, actual);
if (expected.getResult() == DocWriteResponse.Result.CREATED) {
assertTrue((boolean) actual.get("created"));
} else {
assertFalse((boolean) actual.get("created"));
}
}
private static IndexResponse randomIndexResponse() {
ShardId shardId = new ShardId(randomAsciiOfLength(5), randomAsciiOfLength(5), randomIntBetween(0, 5));
String type = randomAsciiOfLength(5);

View File

@ -0,0 +1,141 @@
/*
* 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.action.update;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.index.IndexResponseTests;
import org.elasticsearch.action.support.replication.ReplicationResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.get.GetField;
import org.elasticsearch.index.get.GetResult;
import org.elasticsearch.index.get.GetResultTests;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.RandomObjects;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.action.DocWriteResponse.Result.DELETED;
import static org.elasticsearch.action.DocWriteResponse.Result.NOT_FOUND;
import static org.elasticsearch.action.DocWriteResponse.Result.UPDATED;
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
public class UpdateResponseTests extends ESTestCase {
public void testToXContent() throws IOException {
{
UpdateResponse updateResponse = new UpdateResponse(new ShardId("index", "index_uuid", 0), "type", "id", 0, NOT_FOUND);
String output = Strings.toString(updateResponse);
assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":0,\"result\":\"not_found\"," +
"\"_shards\":{\"total\":0,\"successful\":0,\"failed\":0}}", output);
}
{
UpdateResponse updateResponse = new UpdateResponse(new ReplicationResponse.ShardInfo(10, 6),
new ShardId("index", "index_uuid", 1), "type", "id", 3, 1, DELETED);
String output = Strings.toString(updateResponse);
assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":1,\"result\":\"deleted\"," +
"\"_shards\":{\"total\":10,\"successful\":6,\"failed\":0},\"_seq_no\":3}", output);
}
{
BytesReference source = new BytesArray("{\"title\":\"Book title\",\"isbn\":\"ABC-123\"}");
Map<String, GetField> fields = new HashMap<>();
fields.put("title", new GetField("title", Collections.singletonList("Book title")));
fields.put("isbn", new GetField("isbn", Collections.singletonList("ABC-123")));
UpdateResponse updateResponse = new UpdateResponse(new ReplicationResponse.ShardInfo(3, 2),
new ShardId("books", "books_uuid", 2), "book", "1", 7, 2, UPDATED);
updateResponse.setGetResult(new GetResult("books", "book", "1", 2, true, source, fields));
String output = Strings.toString(updateResponse);
assertEquals("{\"_index\":\"books\",\"_type\":\"book\",\"_id\":\"1\",\"_version\":2,\"result\":\"updated\"," +
"\"_shards\":{\"total\":3,\"successful\":2,\"failed\":0},\"_seq_no\":7,\"get\":{\"found\":true," +
"\"_source\":{\"title\":\"Book title\",\"isbn\":\"ABC-123\"},\"fields\":{\"isbn\":[\"ABC-123\"],\"title\":[\"Book " +
"title\"]}}}", output);
}
}
public void testToAndFromXContent() throws IOException {
final XContentType xContentType = randomFrom(XContentType.values());
final Tuple<UpdateResponse, UpdateResponse> tuple = randomUpdateResponse(xContentType);
// Parse the XContent bytes to obtain a parsed UpdateResponse
UpdateResponse parsedUpdateResponse;
try (XContentParser parser = createParser(xContentType.xContent(), toXContent(tuple.v1(), xContentType))) {
parsedUpdateResponse = UpdateResponse.fromXContent(parser);
assertNull(parser.nextToken());
}
final UpdateResponse expectedUpdateResponse = tuple.v2();
try (XContentParser parser = createParser(xContentType.xContent(), toXContent(parsedUpdateResponse, xContentType))) {
IndexResponseTests.assertDocWriteResponse(expectedUpdateResponse, parser.map());
}
assertEquals(expectedUpdateResponse.getGetResult(), parsedUpdateResponse.getGetResult());
}
private static Tuple<UpdateResponse, UpdateResponse> randomUpdateResponse(XContentType xContentType) {
Tuple<GetResult, GetResult> getResults = GetResultTests.randomGetResult(xContentType);
GetResult actualGetResult = getResults.v1();
GetResult expectedGetResult = getResults.v2();
ShardId shardId = new ShardId(actualGetResult.getIndex(), randomAsciiOfLength(5), randomIntBetween(0, 5));
String type = actualGetResult.getType();
String id = actualGetResult.getId();
long version = actualGetResult.getVersion();
DocWriteResponse.Result result = actualGetResult.isExists() ? DocWriteResponse.Result.UPDATED : DocWriteResponse.Result.NOT_FOUND;
// We also want small number values (randomNonNegativeLong() tend to generate high numbers)
// in order to catch some conversion error that happen between int/long after parsing.
Long seqNo = randomFrom(randomNonNegativeLong(), (long) randomIntBetween(0, 10_000), null);
UpdateResponse actual, expected;
if (seqNo != null) {
ReplicationResponse.ShardInfo shardInfo = RandomObjects.randomShardInfo(random(), true);
actual = new UpdateResponse(shardInfo, shardId, type, id, seqNo, version, result);
expected = new UpdateResponse(shardInfo, shardId, type, id, seqNo, version, result);
} else {
actual = new UpdateResponse(shardId, type, id, version, result);
expected = new UpdateResponse(shardId, type, id, version, result);
}
if (actualGetResult.isExists()) {
actual.setGetResult(actualGetResult);
}
if (expectedGetResult.isExists()) {
expected.setGetResult(new GetResult(shardId.getIndexName(), type, id, version,
expectedGetResult.isExists(), expectedGetResult.internalSourceRef(), expectedGetResult.getFields()));
}
boolean forcedRefresh = randomBoolean();
actual.setForcedRefresh(forcedRefresh);
expected.setForcedRefresh(forcedRefresh);
return Tuple.tuple(actual, expected);
}
}

View File

@ -23,6 +23,7 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
@ -30,13 +31,16 @@ import org.elasticsearch.test.RandomObjects;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
import static org.elasticsearch.index.get.GetFieldTests.randomGetField;
import static org.elasticsearch.test.EqualsHashCodeTestUtils.checkEqualsAndHashCode;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
@ -66,8 +70,8 @@ public class GetResultTests extends ESTestCase {
public void testToXContent() throws IOException {
{
GetResult getResult = new GetResult("index", "type", "id", 1, true, new BytesArray("{ \"field1\" : " +
"\"value1\", \"field2\":\"value2\"}"), Collections.singletonMap("field1", new GetField("field1",
Collections.singletonList("value1"))));
"\"value1\", \"field2\":\"value2\"}"), singletonMap("field1", new GetField("field1",
singletonList("value1"))));
String output = Strings.toString(getResult);
assertEquals("{\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":1,\"found\":true,\"_source\":{ \"field1\" " +
": \"value1\", \"field2\":\"value2\"},\"fields\":{\"field1\":[\"value1\"]}}", output);
@ -79,6 +83,54 @@ public class GetResultTests extends ESTestCase {
}
}
public void testToAndFromXContentEmbedded() throws Exception {
XContentType xContentType = randomFrom(XContentType.values());
Tuple<GetResult, GetResult> tuple = randomGetResult(xContentType);
GetResult getResult = tuple.v1();
// We don't expect to retrieve the index/type/id of the GetResult because they are not rendered
// by the toXContentEmbedded method.
GetResult expectedGetResult = new GetResult(null, null, null, -1,
tuple.v2().isExists(), tuple.v2().sourceRef(), tuple.v2().getFields());
BytesReference originalBytes = toXContentEmbedded(getResult, xContentType);
// Test that we can parse the result of toXContentEmbedded()
GetResult parsedEmbeddedGetResult;
try (XContentParser parser = createParser(xContentType.xContent(), originalBytes)) {
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
parsedEmbeddedGetResult = GetResult.fromXContentEmbedded(parser);
assertNull(parser.nextToken());
}
assertEquals(expectedGetResult, parsedEmbeddedGetResult);
//print the parsed object out and test that the output is the same as the original output
BytesReference finalBytes = toXContentEmbedded(parsedEmbeddedGetResult, xContentType);
assertToXContentEquivalent(originalBytes, finalBytes, xContentType);
//check that the source stays unchanged, no shuffling of keys nor anything like that
assertEquals(expectedGetResult.sourceAsString(), parsedEmbeddedGetResult.sourceAsString());
}
public void testToXContentEmbedded() throws IOException {
Map<String, GetField> fields = new HashMap<>();
fields.put("foo", new GetField("foo", singletonList("bar")));
fields.put("baz", new GetField("baz", Arrays.asList("baz_0", "baz_1")));
GetResult getResult = new GetResult("index", "type", "id", 2, true,
new BytesArray("{\"foo\":\"bar\",\"baz\":[\"baz_0\",\"baz_1\"]}"), fields);
BytesReference originalBytes = toXContentEmbedded(getResult, XContentType.JSON);
assertEquals("{\"found\":true,\"_source\":{\"foo\":\"bar\",\"baz\":[\"baz_0\",\"baz_1\"]}," +
"\"fields\":{\"foo\":[\"bar\"],\"baz\":[\"baz_0\",\"baz_1\"]}}", originalBytes.utf8ToString());
}
public void testToXContentEmbeddedNotFound() throws IOException {
GetResult getResult = new GetResult("index", "type", "id", 1, false, null, null);
BytesReference originalBytes = toXContentEmbedded(getResult, XContentType.JSON);
assertEquals("{\"found\":false}", originalBytes.utf8ToString());
}
public void testGetSourceAsBytes() {
XContentType xContentType = randomFrom(XContentType.values());
Tuple<GetResult, GetResult> tuple = randomGetResult(xContentType);
@ -160,4 +212,8 @@ public class GetResultTests extends ESTestCase {
}
return Tuple.tuple(fields, expectedFields);
}
private static BytesReference toXContentEmbedded(GetResult getResult, XContentType xContentType) throws IOException {
return XContentHelper.toXContent(getResult::toXContentEmbedded, xContentType);
}
}