/* * 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.search; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ShardOperationFailedException; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.RestStatus; import java.io.IOException; import java.util.*; /** * */ public class SearchPhaseExecutionException extends ElasticsearchException { private final String phaseName; private final ShardSearchFailure[] shardFailures; public SearchPhaseExecutionException(String phaseName, String msg, ShardSearchFailure[] shardFailures) { super(msg); this.phaseName = phaseName; this.shardFailures = shardFailures; } public SearchPhaseExecutionException(String phaseName, String msg, Throwable cause, ShardSearchFailure[] shardFailures) { super(msg, cause); this.phaseName = phaseName; this.shardFailures = shardFailures; } public SearchPhaseExecutionException(StreamInput in) throws IOException { super(in); phaseName = in.readOptionalString(); int numFailures = in.readVInt(); shardFailures = new ShardSearchFailure[numFailures]; for (int i = 0; i < numFailures; i++) { shardFailures[i] = ShardSearchFailure.readShardSearchFailure(in); } } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeOptionalString(phaseName); out.writeVInt(shardFailures == null ? 0 : shardFailures.length); if (shardFailures != null) { for (ShardSearchFailure failure : shardFailures) { failure.writeTo(out); } } } @Override public RestStatus status() { if (shardFailures.length == 0) { // if no successful shards, it means no active shards, so just return SERVICE_UNAVAILABLE return RestStatus.SERVICE_UNAVAILABLE; } RestStatus status = shardFailures[0].status(); if (shardFailures.length > 1) { for (int i = 1; i < shardFailures.length; i++) { if (shardFailures[i].status().getStatus() >= 500) { status = shardFailures[i].status(); } } } return status; } public ShardSearchFailure[] shardFailures() { return shardFailures; } public Throwable getCause() { Throwable cause = super.getCause(); if (cause == null) { // fall back to guessed root cause for (ElasticsearchException rootCause : guessRootCauses()) { return rootCause; } } return cause; } private static String buildMessage(String phaseName, String msg, ShardSearchFailure[] shardFailures) { StringBuilder sb = new StringBuilder(); sb.append("Failed to execute phase [").append(phaseName).append("], ").append(msg); if (shardFailures != null && shardFailures.length > 0) { sb.append("; shardFailures "); for (ShardSearchFailure shardFailure : shardFailures) { if (shardFailure.shard() != null) { sb.append("{").append(shardFailure.shard()).append(": ").append(shardFailure.reason()).append("}"); } else { sb.append("{").append(shardFailure.reason()).append("}"); } } } return sb.toString(); } @Override protected void innerToXContent(XContentBuilder builder, Params params) throws IOException { builder.field("phase", phaseName); final boolean group = params.paramAsBoolean("group_shard_failures", true); // we group by default builder.field("grouped", group); // notify that it's grouped builder.field("failed_shards"); builder.startArray(); ShardOperationFailedException[] failures = params.paramAsBoolean("group_shard_failures", true) ? ExceptionsHelper.groupBy(shardFailures) : shardFailures; for (ShardOperationFailedException failure : failures) { builder.startObject(); failure.toXContent(builder, params); builder.endObject(); } builder.endArray(); super.innerToXContent(builder, params); } @Override protected void causeToXContent(XContentBuilder builder, Params params) throws IOException { if (super.getCause() != null) { // if the cause is null we inject a guessed root cause that will then be rendered twice so wi disable it manually super.causeToXContent(builder, params); } } @Override public ElasticsearchException[] guessRootCauses() { ShardOperationFailedException[] failures = ExceptionsHelper.groupBy(shardFailures); List<ElasticsearchException> rootCauses = new ArrayList<>(failures.length); for (ShardOperationFailedException failure : failures) { ElasticsearchException[] guessRootCauses = ElasticsearchException.guessRootCauses(failure.getCause()); rootCauses.addAll(Arrays.asList(guessRootCauses)); } return rootCauses.toArray(new ElasticsearchException[0]); } @Override public String toString() { return buildMessage(phaseName, getMessage(), shardFailures); } public String getPhaseName() { return phaseName; } }