Add parsing method for ElasticsearchException.generateFailureXContent() (#22815)

This commit adds a ElasticsearchException.failureFromXContent() that can be used to parse the result of ElasticsearchException.generateFailureXContent().
This commit is contained in:
Tanguy Leroux 2017-01-27 10:12:58 +01:00 committed by GitHub
parent 1baa884ab7
commit ea7077fb1b
2 changed files with 116 additions and 24 deletions

View File

@ -51,6 +51,7 @@ import static java.util.Collections.singletonMap;
import static java.util.Collections.unmodifiableMap;
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE;
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
/**
* A base class for all elasticsearch exceptions.
@ -478,16 +479,7 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
}
}
StringBuilder message = new StringBuilder("Elasticsearch exception [");
message.append(TYPE).append('=').append(type).append(", ");
message.append(REASON).append('=').append(reason);
if (stack != null) {
message.append(", ").append(STACK_TRACE).append('=').append(stack);
}
message.append(']');
ElasticsearchException e = new ElasticsearchException(message.toString(), cause);
ElasticsearchException e = new ElasticsearchException(buildMessage(type, reason, stack), cause);
for (Map.Entry<String, List<String>> entry : metadata.entrySet()) {
//subclasses can print out additional metadata through the metadataToXContent method. Simple key-value pairs will be
//parsed back and become part of this metadata set, while objects and arrays are not supported when parsing back.
@ -527,7 +519,8 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
* exception is rendered. When it's true all detail are provided including guesses root causes, cause and potentially stack
* trace.
*
* This method is usually used when the {@link Exception} is rendered as a full XContent object.
* This method is usually used when the {@link Exception} is rendered as a full XContent object, and its output can be parsed
* by the {@link #failureFromXContent(XContentParser)} method.
*/
public static void generateFailureXContent(XContentBuilder builder, Params params, @Nullable Exception e, boolean detailed)
throws IOException {
@ -568,6 +561,27 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
builder.endObject();
}
/**
* Parses the output of {@link #generateFailureXContent(XContentBuilder, Params, Exception, boolean)}
*/
public static ElasticsearchException failureFromXContent(XContentParser parser) throws IOException {
XContentParser.Token token = parser.nextToken();
ensureFieldName(parser, token, ERROR);
token = parser.nextToken();
if (token.isValue()) {
return new ElasticsearchException(buildMessage("exception", parser.text(), null));
}
ensureExpectedToken(token, XContentParser.Token.START_OBJECT, parser::getTokenLocation);
token = parser.nextToken();
// TODO Root causes are ignored for now. They will be skipped by innerFromXContent() because
// it ignores metadata arrays of objects. If we decide to parse root causes, we'll have to
// change innerFromXContent() so that it does not parse root causes on its own.
return innerFromXContent(parser);
}
/**
* Returns the root cause of this exception or multiple if different shards caused different exceptions
*/
@ -613,6 +627,17 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
return toUnderscoreCase(simpleName);
}
static String buildMessage(String type, String reason, String stack) {
StringBuilder message = new StringBuilder("Elasticsearch exception [");
message.append(TYPE).append('=').append(type).append(", ");
message.append(REASON).append('=').append(reason);
if (stack != null) {
message.append(", ").append(STACK_TRACE).append('=').append(stack);
}
message.append(']');
return message.toString();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();

View File

@ -589,22 +589,67 @@ public class ElasticsearchExceptionTests extends ESTestCase {
assertEquals(XContentParser.Token.END_OBJECT, parser.currentToken());
assertNull(parser.nextToken());
}
assertNotNull(parsedException);
assertDeepEquals(exceptions.v2(), parsedException);
}
public void testUnknownFailureToAndFromXContent() throws IOException {
final XContent xContent = randomFrom(XContentType.values()).xContent();
BytesReference failureBytes = XContentHelper.toXContent((builder, params) -> {
// Prints a null failure using generateFailureXContent()
ElasticsearchException.generateFailureXContent(builder, params, null, randomBoolean());
return builder;
}, xContent.type(), randomBoolean());
ElasticsearchException parsedFailure;
try (XContentParser parser = createParser(xContent, failureBytes)) {
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
parsedFailure = ElasticsearchException.failureFromXContent(parser);
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
assertNull(parser.nextToken());
}
// Failure was null, expecting a "unknown" reason
assertEquals("Elasticsearch exception [type=exception, reason=unknown]", parsedFailure.getMessage());
assertEquals(0, parsedFailure.getHeaders().size());
assertEquals(0, parsedFailure.getMetadata().size());
}
public void testFailureToAndFromXContent() throws IOException {
final XContent xContent = randomFrom(XContentType.values()).xContent();
final Tuple<Throwable, ElasticsearchException> exceptions = randomExceptions();
final boolean detailed = randomBoolean();
Exception failure = (Exception) exceptions.v1();
BytesReference failureBytes = XContentHelper.toXContent((builder, params) -> {
ElasticsearchException.generateFailureXContent(builder, params, failure, detailed);
return builder;
}, xContent.type(), randomBoolean());
ElasticsearchException parsedFailure;
try (XContentParser parser = createParser(xContent, failureBytes)) {
assertEquals(XContentParser.Token.START_OBJECT, parser.nextToken());
parsedFailure = ElasticsearchException.failureFromXContent(parser);
assertEquals(XContentParser.Token.END_OBJECT, parser.nextToken());
assertNull(parser.nextToken());
}
assertNotNull(parsedFailure);
ElasticsearchException expected = exceptions.v2();
do {
assertEquals(expected.getMessage(), parsedException.getMessage());
assertEquals(expected.getHeaders(), parsedException.getHeaders());
assertEquals(expected.getMetadata(), parsedException.getMetadata());
assertEquals(expected.getResourceType(), parsedException.getResourceType());
assertEquals(expected.getResourceId(), parsedException.getResourceId());
expected = (ElasticsearchException) expected.getCause();
parsedException = (ElasticsearchException) parsedException.getCause();
if (expected == null) {
assertNull(parsedException);
if (detailed) {
assertDeepEquals(expected, parsedFailure);
} else {
String reason;
if (failure instanceof ElasticsearchException) {
reason = failure.getClass().getSimpleName() + "[" + failure.getMessage() + "]";
} else {
reason = "No ElasticsearchException found";
}
} while (expected != null);
assertEquals(ElasticsearchException.buildMessage("exception", reason, null), parsedFailure.getMessage());
assertEquals(0, parsedFailure.getHeaders().size());
assertEquals(0, parsedFailure.getMetadata().size());
assertNull(parsedFailure.getCause());
}
}
/**
@ -625,6 +670,28 @@ public class ElasticsearchExceptionTests extends ESTestCase {
}, expectedJson);
}
private static void assertDeepEquals(ElasticsearchException expected, ElasticsearchException actual) {
if (expected == null) {
assertNull(actual);
} else {
assertNotNull(actual);
}
do {
assertEquals(expected.getMessage(), actual.getMessage());
assertEquals(expected.getHeaders(), actual.getHeaders());
assertEquals(expected.getMetadata(), actual.getMetadata());
assertEquals(expected.getResourceType(), actual.getResourceType());
assertEquals(expected.getResourceId(), actual.getResourceId());
expected = (ElasticsearchException) expected.getCause();
actual = (ElasticsearchException) actual.getCause();
if (expected == null) {
assertNull(actual);
}
} while (expected != null);
}
private static Tuple<Throwable, ElasticsearchException> randomExceptions() {
Throwable actual;
ElasticsearchException expected;