|
36 | 36 | import org.elasticsearch.transport.TcpTransport; |
37 | 37 |
|
38 | 38 | import java.io.IOException; |
| 39 | +import java.util.ArrayList; |
39 | 40 | import java.util.Arrays; |
40 | 41 | import java.util.Collections; |
41 | 42 | import java.util.HashMap; |
|
50 | 51 | import static java.util.Collections.unmodifiableMap; |
51 | 52 | import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_UUID_NA_VALUE; |
52 | 53 | import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; |
53 | | -import static org.elasticsearch.common.xcontent.XContentParserUtils.throwUnknownField; |
54 | 54 |
|
55 | 55 | /** |
56 | 56 | * A base class for all elasticsearch exceptions. |
@@ -391,12 +391,125 @@ private static void headerToXContent(XContentBuilder builder, String key, List<S |
391 | 391 | protected void metadataToXContent(XContentBuilder builder, Params params) throws IOException { |
392 | 392 | } |
393 | 393 |
|
| 394 | + /** |
| 395 | + * Generate a {@link ElasticsearchException} from a {@link XContentParser}. This does not |
| 396 | + * return the original exception type (ie NodeClosedException for example) but just wraps |
| 397 | + * the type, the reason and the cause of the exception. It also recursively parses the |
| 398 | + * tree structure of the cause, returning it as a tree structure of {@link ElasticsearchException} |
| 399 | + * instances. |
| 400 | + */ |
| 401 | + public static ElasticsearchException fromXContent(XContentParser parser) throws IOException { |
| 402 | + XContentParser.Token token = parser.nextToken(); |
| 403 | + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); |
| 404 | + return innerFromXContent(parser); |
| 405 | + } |
| 406 | + |
| 407 | + private static ElasticsearchException innerFromXContent(XContentParser parser) throws IOException { |
| 408 | + XContentParser.Token token = parser.currentToken(); |
| 409 | + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); |
| 410 | + |
| 411 | + String type = null, reason = null, stack = null; |
| 412 | + ElasticsearchException cause = null; |
| 413 | + Map<String, List<String>> metadata = new HashMap<>(); |
| 414 | + Map<String, List<String>> headers = new HashMap<>(); |
| 415 | + |
| 416 | + for (; token == XContentParser.Token.FIELD_NAME; token = parser.nextToken()) { |
| 417 | + String currentFieldName = parser.currentName(); |
| 418 | + token = parser.nextToken(); |
| 419 | + |
| 420 | + if (token.isValue()) { |
| 421 | + if (TYPE.equals(currentFieldName)) { |
| 422 | + type = parser.text(); |
| 423 | + } else if (REASON.equals(currentFieldName)) { |
| 424 | + reason = parser.text(); |
| 425 | + } else if (STACK_TRACE.equals(currentFieldName)) { |
| 426 | + stack = parser.text(); |
| 427 | + } else if (token == XContentParser.Token.VALUE_STRING) { |
| 428 | + metadata.put(currentFieldName, Collections.singletonList(parser.text())); |
| 429 | + } |
| 430 | + } else if (token == XContentParser.Token.START_OBJECT) { |
| 431 | + if (CAUSED_BY.equals(currentFieldName)) { |
| 432 | + cause = fromXContent(parser); |
| 433 | + } else if (HEADER.equals(currentFieldName)) { |
| 434 | + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { |
| 435 | + if (token == XContentParser.Token.FIELD_NAME) { |
| 436 | + currentFieldName = parser.currentName(); |
| 437 | + } else { |
| 438 | + List<String> values = headers.getOrDefault(currentFieldName, new ArrayList<>()); |
| 439 | + if (token == XContentParser.Token.VALUE_STRING) { |
| 440 | + values.add(parser.text()); |
| 441 | + } else if (token == XContentParser.Token.START_ARRAY) { |
| 442 | + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { |
| 443 | + if (token == XContentParser.Token.VALUE_STRING) { |
| 444 | + values.add(parser.text()); |
| 445 | + } else { |
| 446 | + parser.skipChildren(); |
| 447 | + } |
| 448 | + } |
| 449 | + } else if (token == XContentParser.Token.START_OBJECT) { |
| 450 | + parser.skipChildren(); |
| 451 | + } |
| 452 | + headers.put(currentFieldName, values); |
| 453 | + } |
| 454 | + } |
| 455 | + } else { |
| 456 | + // Any additional metadata object added by the metadataToXContent method is ignored |
| 457 | + // and skipped, so that the parser does not fail on unknown fields. The parser only |
| 458 | + // support metadata key-pairs and metadata arrays of values. |
| 459 | + parser.skipChildren(); |
| 460 | + } |
| 461 | + } else if (token == XContentParser.Token.START_ARRAY) { |
| 462 | + // Parse the array and add each item to the corresponding list of metadata. |
| 463 | + // Arrays of objects are not supported yet and just ignored and skipped. |
| 464 | + List<String> values = new ArrayList<>(); |
| 465 | + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { |
| 466 | + if (token == XContentParser.Token.VALUE_STRING) { |
| 467 | + values.add(parser.text()); |
| 468 | + } else { |
| 469 | + parser.skipChildren(); |
| 470 | + } |
| 471 | + } |
| 472 | + if (values.size() > 0) { |
| 473 | + if (metadata.containsKey(currentFieldName)) { |
| 474 | + values.addAll(metadata.get(currentFieldName)); |
| 475 | + } |
| 476 | + metadata.put(currentFieldName, values); |
| 477 | + } |
| 478 | + } |
| 479 | + } |
| 480 | + |
| 481 | + StringBuilder message = new StringBuilder("Elasticsearch exception ["); |
| 482 | + message.append(TYPE).append('=').append(type).append(", "); |
| 483 | + message.append(REASON).append('=').append(reason); |
| 484 | + if (stack != null) { |
| 485 | + message.append(", ").append(STACK_TRACE).append('=').append(stack); |
| 486 | + } |
| 487 | + message.append(']'); |
| 488 | + |
| 489 | + ElasticsearchException e = new ElasticsearchException(message.toString(), cause); |
| 490 | + |
| 491 | + for (Map.Entry<String, List<String>> entry : metadata.entrySet()) { |
| 492 | + //subclasses can print out additional metadata through the metadataToXContent method. Simple key-value pairs will be |
| 493 | + //parsed back and become part of this metadata set, while objects and arrays are not supported when parsing back. |
| 494 | + //Those key-value pairs become part of the metadata set and inherit the "es." prefix as that is currently required |
| 495 | + //by addMetadata. The prefix will get stripped out when printing metadata out so it will be effectively invisible. |
| 496 | + //TODO move subclasses that print out simple metadata to using addMetadata directly and support also numbers and booleans. |
| 497 | + //TODO rename metadataToXContent and have only SearchPhaseExecutionException use it, which prints out complex objects |
| 498 | + e.addMetadata("es." + entry.getKey(), entry.getValue()); |
| 499 | + } |
| 500 | + for (Map.Entry<String, List<String>> header : headers.entrySet()) { |
| 501 | + e.addHeader(header.getKey(), header.getValue()); |
| 502 | + } |
| 503 | + return e; |
| 504 | + } |
| 505 | + |
394 | 506 | /** |
395 | 507 | * Static toXContent helper method that renders {@link org.elasticsearch.ElasticsearchException} or {@link Throwable} instances |
396 | 508 | * as XContent, delegating the rendering to {@link #toXContent(XContentBuilder, Params)} |
397 | 509 | * or {@link #innerToXContent(XContentBuilder, Params, Throwable, String, String, Map, Map, Throwable)}. |
398 | 510 | * |
399 | | - * This method is usually used when the {@link Throwable} is rendered as a part of another XContent object. |
| 511 | + * This method is usually used when the {@link Throwable} is rendered as a part of another XContent object, and its result can |
| 512 | + * be parsed back using the {@link #fromXContent(XContentParser)} method. |
400 | 513 | */ |
401 | 514 | public static void generateThrowableXContent(XContentBuilder builder, Params params, Throwable t) throws IOException { |
402 | 515 | t = ExceptionsHelper.unwrapCause(t); |
@@ -455,71 +568,6 @@ public static void generateFailureXContent(XContentBuilder builder, Params param |
455 | 568 | builder.endObject(); |
456 | 569 | } |
457 | 570 |
|
458 | | - /** |
459 | | - * Generate a {@link ElasticsearchException} from a {@link XContentParser}. This does not |
460 | | - * return the original exception type (ie NodeClosedException for example) but just wraps |
461 | | - * the type, the reason and the cause of the exception. It also recursively parses the |
462 | | - * tree structure of the cause, returning it as a tree structure of {@link ElasticsearchException} |
463 | | - * instances. |
464 | | - */ |
465 | | - public static ElasticsearchException fromXContent(XContentParser parser) throws IOException { |
466 | | - XContentParser.Token token = parser.nextToken(); |
467 | | - ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); |
468 | | - |
469 | | - String type = null, reason = null, stack = null; |
470 | | - ElasticsearchException cause = null; |
471 | | - Map<String, List<String>> metadata = new HashMap<>(); |
472 | | - Map<String, Object> headers = new HashMap<>(); |
473 | | - |
474 | | - do { |
475 | | - String currentFieldName = parser.currentName(); |
476 | | - token = parser.nextToken(); |
477 | | - if (token.isValue()) { |
478 | | - if (TYPE.equals(currentFieldName)) { |
479 | | - type = parser.text(); |
480 | | - } else if (REASON.equals(currentFieldName)) { |
481 | | - reason = parser.text(); |
482 | | - } else if (STACK_TRACE.equals(currentFieldName)) { |
483 | | - stack = parser.text(); |
484 | | - } else { |
485 | | - metadata.put(currentFieldName, Collections.singletonList(parser.text())); |
486 | | - } |
487 | | - } else if (token == XContentParser.Token.START_OBJECT) { |
488 | | - if (CAUSED_BY.equals(currentFieldName)) { |
489 | | - cause = fromXContent(parser); |
490 | | - } else if (HEADER.equals(currentFieldName)) { |
491 | | - headers.putAll(parser.map()); |
492 | | - } else { |
493 | | - throwUnknownField(currentFieldName, parser.getTokenLocation()); |
494 | | - } |
495 | | - } |
496 | | - } while ((token = parser.nextToken()) == XContentParser.Token.FIELD_NAME); |
497 | | - |
498 | | - StringBuilder message = new StringBuilder("Elasticsearch exception ["); |
499 | | - message.append(TYPE).append('=').append(type).append(", "); |
500 | | - message.append(REASON).append('=').append(reason); |
501 | | - if (stack != null) { |
502 | | - message.append(", ").append(STACK_TRACE).append('=').append(stack); |
503 | | - } |
504 | | - message.append(']'); |
505 | | - |
506 | | - ElasticsearchException e = new ElasticsearchException(message.toString(), cause); |
507 | | - |
508 | | - for (Map.Entry<String, List<String>> entry : metadata.entrySet()) { |
509 | | - //subclasses can print out additional metadata through the metadataToXContent method. Simple key-value pairs will be |
510 | | - //parsed back and become part of this metadata set, while objects and arrays are not supported when parsing back. |
511 | | - //Those key-value pairs become part of the metadata set and inherit the "es." prefix as that is currently required |
512 | | - //by addMetadata. The prefix will get stripped out when printing metadata out so it will be effectively invisible. |
513 | | - //TODO move subclasses that print out simple metadata to using addMetadata directly and support also numbers and booleans. |
514 | | - //TODO rename metadataToXContent and have only SearchPhaseExecutionException use it, which prints out complex objects |
515 | | - e.addMetadata("es." + entry.getKey(), entry.getValue()); |
516 | | - } |
517 | | - for (Map.Entry<String, Object> header : headers.entrySet()) { |
518 | | - e.addHeader(header.getKey(), String.valueOf(header.getValue())); |
519 | | - } |
520 | | - return e; |
521 | | - } |
522 | | - |
523 | 571 | /** |
524 | 572 | * Returns the root cause of this exception or multiple if different shards caused different exceptions |
525 | 573 | */ |
|
0 commit comments