Add fromxcontent methods to delete response

This commit adds the parsing fromXContent() methods to the IndexResponse class.

It's a pale copy of what has been done in #22229.
This commit is contained in:
David Pilato 2017-01-18 15:23:07 +01:00
parent b006636aaf
commit 718a6b9be7
2 changed files with 208 additions and 1 deletions

View File

@ -20,12 +20,21 @@
package org.elasticsearch.action.delete; package org.elasticsearch.action.delete;
import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.seqno.SequenceNumbersService;
import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import java.io.IOException; import java.io.IOException;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
/** /**
* The response of the delete action. * The response of the delete action.
* *
@ -34,6 +43,8 @@ import java.io.IOException;
*/ */
public class DeleteResponse extends DocWriteResponse { public class DeleteResponse extends DocWriteResponse {
private static final String FOUND = "found";
public DeleteResponse() { public DeleteResponse() {
} }
@ -49,11 +60,38 @@ public class DeleteResponse extends DocWriteResponse {
@Override @Override
public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
builder.field("found", result == Result.DELETED); builder.field(FOUND, result == Result.DELETED);
super.innerToXContent(builder, params); super.innerToXContent(builder, params);
return builder; return builder;
} }
/**
* ConstructingObjectParser used to parse the {@link IndexResponse}. We use a ObjectParser here
* because most fields are parsed by the parent abstract class {@link DocWriteResponse} and it's
* not easy to parse part of the fields in the parent class and other fields in the children class
* using the usual streamed parsing method.
*/
private static final ConstructingObjectParser<DeleteResponse, Void> PARSER;
static {
PARSER = new ConstructingObjectParser<>(DeleteResponse.class.getName(),
args -> {
// index uuid and shard id are unknown and can't be parsed back for now.
ShardId shardId = new ShardId(new Index((String) args[0], IndexMetaData.INDEX_UUID_NA_VALUE), -1);
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 found = (boolean) args[6];
return new DeleteResponse(shardId, type, id, seqNo, version, found);
});
DocWriteResponse.declareParserFields(PARSER);
PARSER.declareBoolean(constructorArg(), new ParseField(FOUND));
}
public static DeleteResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.apply(parser, null);
}
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();

View File

@ -0,0 +1,169 @@
/*
* 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.delete;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.RandomObjects;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentHelper.toXContent;
public class DeleteResponseTests extends ESTestCase {
public void testToXContent() throws IOException {
{
DeleteResponse response = new DeleteResponse(new ShardId("index", "index_uuid", 0), "type", "id", 3, 5, true);
String output = Strings.toString(response);
assertEquals("{\"found\":true,\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":5,\"result\":\"deleted\"," +
"\"_shards\":null,\"_seq_no\":3}", output);
}
{
DeleteResponse response = new DeleteResponse(new ShardId("index", "index_uuid", 0), "type", "id", -1, 7, true);
response.setForcedRefresh(true);
response.setShardInfo(new ReplicationResponse.ShardInfo(10, 5));
String output = Strings.toString(response);
assertEquals("{\"found\":true,\"_index\":\"index\",\"_type\":\"type\",\"_id\":\"id\",\"_version\":7,\"result\":\"deleted\"," +
"\"forced_refresh\":true,\"_shards\":{\"total\":10,\"successful\":5,\"failed\":0}}", output);
}
}
public void testToAndFromXContent() throws IOException {
final XContentType xContentType = randomFrom(XContentType.values());
// Create a random DeleteResponse and converts it to XContent in bytes
DeleteResponse response = randomDeleteResponse();
BytesReference responseBytes = toXContent(response, xContentType);
// Parse the XContent bytes to obtain a parsed
DeleteResponse parsedDeleteResponse;
try (XContentParser parser = createParser(xContentType.xContent(), responseBytes)) {
parsedDeleteResponse = DeleteResponse.fromXContent(parser);
assertNull(parser.nextToken());
}
// We can't use equals() to compare the original and the parsed index response
// because the random index response can contain shard failures with exceptions,
// and those exceptions are not parsed back with the same types.
// Print the parsed object out and test that the output is the same as the original output
BytesReference parsedDeleteResponseBytes = toXContent(parsedDeleteResponse, xContentType);
try (XContentParser parser = createParser(xContentType.xContent(), parsedDeleteResponseBytes)) {
assertDeleteResponse(response, parser.map());
}
}
private static void assertDeleteResponse(DeleteResponse 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.getResult().getLowercase(), actual.get("result"));
if (expected.forcedRefresh()) {
assertTrue((Boolean) actual.get("forced_refresh"));
} else {
assertFalse(actual.containsKey("forced_refresh"));
}
if (expected.getSeqNo() >= 0) {
assertEquals(expected.getSeqNo(), ((Integer) actual.get("_seq_no")).longValue());
} else {
assertFalse(actual.containsKey("_seq_no"));
}
Map<String, Object> actualShards = (Map<String, Object>) actual.get("_shards");
assertNotNull(actualShards);
assertEquals(expected.getShardInfo().getTotal(), actualShards.get("total"));
assertEquals(expected.getShardInfo().getSuccessful(), actualShards.get("successful"));
assertEquals(expected.getShardInfo().getFailed(), actualShards.get("failed"));
List<Map<String, Object>> actualFailures = (List<Map<String, Object>>) actualShards.get("failures");
if (CollectionUtils.isEmpty(expected.getShardInfo().getFailures())) {
assertNull(actualFailures);
} else {
assertEquals(expected.getShardInfo().getFailures().length, actualFailures.size());
for (int i = 0; i < expected.getShardInfo().getFailures().length; i++) {
ReplicationResponse.ShardInfo.Failure failure = expected.getShardInfo().getFailures()[i];
Map<String, Object> actualFailure = actualFailures.get(i);
assertEquals(failure.index(), actualFailure.get("_index"));
assertEquals(failure.shardId(), actualFailure.get("_shard"));
assertEquals(failure.nodeId(), actualFailure.get("_node"));
assertEquals(failure.status(), RestStatus.valueOf((String) actualFailure.get("status")));
assertEquals(failure.primary(), actualFailure.get("primary"));
Throwable cause = failure.getCause();
Map<String, Object> actualClause = (Map<String, Object>) actualFailure.get("reason");
assertNotNull(actualClause);
while (cause != null) {
// The expected IndexResponse has been converted in XContent, then the resulting bytes have been
// parsed to create a new parsed IndexResponse. During this process, the type of the exceptions
// have been lost.
assertEquals("exception", actualClause.get("type"));
String expectedMessage = "Elasticsearch exception [type=" + ElasticsearchException.getExceptionName(cause)
+ ", reason=" + cause.getMessage() + "]";
assertEquals(expectedMessage, actualClause.get("reason"));
if (cause instanceof ElasticsearchException) {
ElasticsearchException ex = (ElasticsearchException) cause;
Map<String, Object> actualHeaders = (Map<String, Object>) actualClause.get("header");
// When a DeleteResponse is converted to XContent, the exception headers that start with "es."
// are added to the XContent as fields with the prefix removed. Other headers are added under
// a "header" root object.
// In the test, the "es." prefix is lost when the XContent is generating, so when the parsed
// IndexResponse is converted back to XContent all exception headers are under the "header" object.
for (String name : ex.getHeaderKeys()) {
assertEquals(ex.getHeader(name).get(0), actualHeaders.get(name.replaceFirst("es.", "")));
}
}
actualClause = (Map<String, Object>) actualClause.get("caused_by");
cause = cause.getCause();
}
}
}
}
private static DeleteResponse randomDeleteResponse() {
ShardId shardId = new ShardId(randomAsciiOfLength(5), randomAsciiOfLength(5), randomIntBetween(0, 5));
String type = randomAsciiOfLength(5);
String id = randomAsciiOfLength(5);
long seqNo = randomIntBetween(-2, 5);
long version = (long) randomIntBetween(0, 5);
boolean created = randomBoolean();
DeleteResponse response = new DeleteResponse(shardId, type, id, seqNo, version, created);
response.setForcedRefresh(randomBoolean());
response.setShardInfo(RandomObjects.randomShardInfo(random(), randomBoolean()));
return response;
}
}