/*
 * 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;

import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.logging.support.LoggerMessageFormat;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.rest.RestStatus;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * A base class for all elasticsearch exceptions.
 */
public class ElasticsearchException extends RuntimeException implements ToXContent {

    public static final String REST_EXCEPTION_SKIP_CAUSE = "rest.exception.cause.skip";
    public static final String REST_EXCEPTION_SKIP_STACK_TRACE = "rest.exception.stacktrace.skip";
    public static final boolean REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT = true;
    public static final boolean REST_EXCEPTION_SKIP_CAUSE_DEFAULT = false;
    private static final String INDEX_HEADER_KEY = "es.index";
    private static final String SHARD_HEADER_KEY = "es.shard";
    private static final String RESOURCE_HEADER_TYPE_KEY = "es.resource.type";
    private static final String RESOURCE_HEADER_ID_KEY = "es.resource.id";

    static final Map<Integer, Constructor<? extends ElasticsearchException>> ID_TO_SUPPLIER;
    private static final Map<Class<? extends ElasticsearchException>, Integer> CLASS_TO_ID;
    private final Map<String, List<String>> headers = new HashMap<>();

    public ElasticsearchException(Throwable cause) {
        super(cause);
    }

    /**
     * Construct a <code>ElasticsearchException</code> with the specified detail message.
     *
     * The message can be parameterized using <code>{}</code> as placeholders for the given
     * arguments
     *
     * @param msg the detail message
     * @param args the arguments for the message
     */
    public ElasticsearchException(String msg, Object... args) {
        super(LoggerMessageFormat.format(msg, args));
    }

    /**
     * Construct a <code>ElasticsearchException</code> with the specified detail message
     * and nested exception.
     *
     * The message can be parameterized using <code>{}</code> as placeholders for the given
     * arguments
     *
     * @param msg   the detail message
     * @param cause the nested exception
     * @param args  the arguments for the message
     */
    public ElasticsearchException(String msg, Throwable cause, Object... args) {
        super(LoggerMessageFormat.format(msg, args), cause);
    }

    public ElasticsearchException(StreamInput in) throws IOException {
        super(in.readOptionalString(), in.readThrowable());
        readStackTrace(this, in);
        int numKeys = in.readVInt();
        for (int i = 0; i < numKeys; i++) {
            final String key = in.readString();
            final int numValues = in.readVInt();
            final ArrayList<String> values = new ArrayList<>(numValues);
            for (int j = 0; j < numValues; j++) {
                values.add(in.readString());
            }
            headers.put(key, values);
        }
    }

    /**
     * Adds a new header with the given key.
     * This method will replace existing header if a header with the same key already exists
     */
    public void addHeader(String key, String... value) {
        this.headers.put(key, Arrays.asList(value));
    }

    /**
     * Adds a new header with the given key.
     * This method will replace existing header if a header with the same key already exists
     */
    public void addHeader(String key, List<String> value) {
        this.headers.put(key, value);
    }


    /**
     * Returns a set of all header keys on this exception
     */
    public Set<String> getHeaderKeys() {
        return headers.keySet();
    }

    /**
     * Returns the list of header values for the given key or {@code null} if not header for the
     * given key exists.
     */
    public List<String> getHeader(String key) {
        return headers.get(key);
    }

    /**
     * Returns the rest status code associated with this exception.
     */
    public RestStatus status() {
        Throwable cause = unwrapCause();
        if (cause == this) {
            return RestStatus.INTERNAL_SERVER_ERROR;
        } else {
            return ExceptionsHelper.status(cause);
        }
    }

    /**
     * Unwraps the actual cause from the exception for cases when the exception is a
     * {@link ElasticsearchWrapperException}.
     *
     * @see org.elasticsearch.ExceptionsHelper#unwrapCause(Throwable)
     */
    public Throwable unwrapCause() {
        return ExceptionsHelper.unwrapCause(this);
    }

    /**
     * Return the detail message, including the message from the nested exception
     * if there is one.
     */
    public String getDetailedMessage() {
        if (getCause() != null) {
            StringBuilder sb = new StringBuilder();
            sb.append(toString()).append("; ");
            if (getCause() instanceof ElasticsearchException) {
                sb.append(((ElasticsearchException) getCause()).getDetailedMessage());
            } else {
                sb.append(getCause());
            }
            return sb.toString();
        } else {
            return super.toString();
        }
    }


    /**
     * Retrieve the innermost cause of this exception, if none, returns the current exception.
     */
    public Throwable getRootCause() {
        Throwable rootCause = this;
        Throwable cause = getCause();
        while (cause != null && cause != rootCause) {
            rootCause = cause;
            cause = cause.getCause();
        }
        return rootCause;
    }

    /**
     * Check whether this exception contains an exception of the given type:
     * either it is of the given class itself or it contains a nested cause
     * of the given type.
     *
     * @param exType the exception type to look for
     * @return whether there is a nested exception of the specified type
     */
    public boolean contains(Class exType) {
        if (exType == null) {
            return false;
        }
        if (exType.isInstance(this)) {
            return true;
        }
        Throwable cause = getCause();
        if (cause == this) {
            return false;
        }
        if (cause instanceof ElasticsearchException) {
            return ((ElasticsearchException) cause).contains(exType);
        } else {
            while (cause != null) {
                if (exType.isInstance(cause)) {
                    return true;
                }
                if (cause.getCause() == cause) {
                    break;
                }
                cause = cause.getCause();
            }
            return false;
        }
    }

    public void writeTo(StreamOutput out) throws IOException {
        out.writeOptionalString(this.getMessage());
        out.writeThrowable(this.getCause());
        writeStackTraces(this, out);
        out.writeVInt(headers.size());
        for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
            out.writeString(entry.getKey());
            out.writeVInt(entry.getValue().size());
            for (String v : entry.getValue()) {
                out.writeString(v);
            }
        }
    }

    public static ElasticsearchException readException(StreamInput input, int id) throws IOException {
        Constructor<? extends ElasticsearchException> elasticsearchException = ID_TO_SUPPLIER.get(id);
        if (elasticsearchException == null) {
            throw new IllegalStateException("unknown exception for id: " + id);
        }
        try {
            return elasticsearchException.newInstance(input);
        } catch (InstantiationException|IllegalAccessException|InvocationTargetException e) {
            throw new IOException("failed to read exception for id [" + id + "]", e);
        }
    }

    /**
     * Retruns <code>true</code> iff the given class is a registered for an exception to be read.
     */
    public static boolean isRegistered(Class<? extends Throwable> exception) {
        return CLASS_TO_ID.containsKey(exception);
    }

    static Set<Class<? extends ElasticsearchException>> getRegisteredKeys() { // for testing
        return CLASS_TO_ID.keySet();
    }

    /**
     * Returns the serialization id the given exception.
     */
    public static int getId(Class<? extends ElasticsearchException> exception) {
        return CLASS_TO_ID.get(exception).intValue();
    }

    @Override
    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
        Throwable ex = ExceptionsHelper.unwrapCause(this);
        if (ex != this) {
            toXContent(builder, params, this);
        } else {
            builder.field("type", getExceptionName());
            builder.field("reason", getMessage());
            for (String key : headers.keySet()) {
                if (key.startsWith("es.")) {
                    List<String> values = headers.get(key);
                    xContentHeader(builder, key.substring("es.".length()), values);
                }
            }
            innerToXContent(builder, params);
            renderHeader(builder, params);
            if (params.paramAsBoolean(REST_EXCEPTION_SKIP_STACK_TRACE, REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT) == false) {
                builder.field("stack_trace", ExceptionsHelper.stackTrace(this));
            }
        }
        return builder;
    }

    /**
     * Renders additional per exception information into the xcontent
     */
    protected void innerToXContent(XContentBuilder builder, Params params) throws IOException {
        causeToXContent(builder, params);
    }

    /**
     * Renders a cause exception as xcontent
     */
    protected void causeToXContent(XContentBuilder builder, Params params) throws IOException {
        final Throwable cause = getCause();
        if (cause != null && params.paramAsBoolean(REST_EXCEPTION_SKIP_CAUSE, REST_EXCEPTION_SKIP_CAUSE_DEFAULT) == false) {
            builder.field("caused_by");
            builder.startObject();
            toXContent(builder, params, cause);
            builder.endObject();
        }
    }

    protected final void renderHeader(XContentBuilder builder, Params params) throws IOException {
        boolean hasHeader = false;
        for (String key : headers.keySet()) {
            if (key.startsWith("es.")) {
                continue;
            }
            if (hasHeader == false) {
                builder.startObject("header");
                hasHeader = true;
            }
            List<String> values = headers.get(key);
            xContentHeader(builder, key, values);
        }
        if (hasHeader) {
            builder.endObject();
        }
    }

    private void xContentHeader(XContentBuilder builder, String key, List<String> values) throws IOException {
        if (values != null && values.isEmpty() == false) {
            if(values.size() == 1) {
                builder.field(key, values.get(0));
            } else {
                builder.startArray(key);
                for (String value : values) {
                    builder.value(value);
                }
                builder.endArray();
            }
        }
    }

    /**
     * Statis toXContent helper method that also renders non {@link org.elasticsearch.ElasticsearchException} instances as XContent.
     */
    public static void toXContent(XContentBuilder builder, Params params, Throwable ex) throws IOException {
        ex = ExceptionsHelper.unwrapCause(ex);
        if (ex instanceof ElasticsearchException) {
            ((ElasticsearchException) ex).toXContent(builder, params);
        } else {
            builder.field("type", getExceptionName(ex));
            builder.field("reason", ex.getMessage());
            if (ex.getCause() != null) {
                builder.field("caused_by");
                builder.startObject();
                toXContent(builder, params, ex.getCause());
                builder.endObject();
            }
            if (params.paramAsBoolean(REST_EXCEPTION_SKIP_STACK_TRACE, REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT) == false) {
                builder.field("stack_trace", ExceptionsHelper.stackTrace(ex));
            }
        }
    }

    /**
     * Returns the root cause of this exception or mupltiple if different shards caused different exceptions
     */
    public ElasticsearchException[] guessRootCauses() {
        final Throwable cause = getCause();
        if (cause != null && cause instanceof ElasticsearchException) {
            return ((ElasticsearchException) cause).guessRootCauses();
        }
        return new ElasticsearchException[] {this};
    }

    /**
     * Returns the root cause of this exception or mupltiple if different shards caused different exceptions.
     * If the given exception is not an instance of {@link org.elasticsearch.ElasticsearchException} an empty array
     * is returned.
     */
    public static ElasticsearchException[] guessRootCauses(Throwable t) {
        Throwable ex = ExceptionsHelper.unwrapCause(t);
        if (ex instanceof ElasticsearchException) {
            return ((ElasticsearchException) ex).guessRootCauses();
        }
        return new ElasticsearchException[] {new ElasticsearchException(t.getMessage(), t) {
            @Override
            protected String getExceptionName() {
                return getExceptionName(getCause());
            }
        }};
    }

    protected String getExceptionName() {
        return getExceptionName(this);
    }

    /**
     * Returns a underscore case name for the given exception. This method strips <tt>Elasticsearch</tt> prefixes from exception names.
     */
    public static String getExceptionName(Throwable ex) {
        String simpleName = ex.getClass().getSimpleName();
        if (simpleName.startsWith("Elasticsearch")) {
            simpleName = simpleName.substring("Elasticsearch".length());
        }
        return Strings.toUnderscoreCase(simpleName);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        if (headers.containsKey(INDEX_HEADER_KEY)) {
            builder.append('[').append(getIndex()).append(']');
            if (headers.containsKey(SHARD_HEADER_KEY)) {
                builder.append('[').append(getShardId()).append(']');
            }
            builder.append(' ');
        }
        return builder.append(ExceptionsHelper.detailedMessage(this).trim()).toString();
    }

    /**
     * Deserializes stacktrace elements as well as suppressed exceptions from the given output stream and
     * adds it to the given exception.
     */
    public static <T extends Throwable> T readStackTrace(T throwable, StreamInput in) throws IOException {
        final int stackTraceElements = in.readVInt();
        StackTraceElement[] stackTrace = new StackTraceElement[stackTraceElements];
        for (int i = 0; i < stackTraceElements; i++) {
            final String declaringClasss = in.readString();
            final String fileName = in.readOptionalString();
            final String methodName = in.readString();
            final int lineNumber = in.readVInt();
            stackTrace[i] = new StackTraceElement(declaringClasss,methodName, fileName, lineNumber);
        }
        throwable.setStackTrace(stackTrace);

        int numSuppressed = in.readVInt();
        for (int i = 0; i < numSuppressed; i++) {
            throwable.addSuppressed(in.readThrowable());
        }
        return throwable;
    }

    /**
     * Serializes the given exceptions stacktrace elements as well as it's suppressed exceptions to the given output stream.
     */
    public static <T extends Throwable> T writeStackTraces(T throwable, StreamOutput out) throws IOException {
        StackTraceElement[] stackTrace = throwable.getStackTrace();
        out.writeVInt(stackTrace.length);
        for (StackTraceElement element : stackTrace) {
            out.writeString(element.getClassName());
            out.writeOptionalString(element.getFileName());
            out.writeString(element.getMethodName());
            out.writeVInt(element.getLineNumber());
        }
        Throwable[] suppressed = throwable.getSuppressed();
        out.writeVInt(suppressed.length);
        for (Throwable t : suppressed) {
            out.writeThrowable(t);
        }
        return throwable;
    }

    static {
        // each exception gets an ordinal assigned that must never change. While the exception name can
        // change due to refactorings etc. like renaming we have to keep the ordinal <--> class mapping
        // to deserialize the exception coming from another node or from an corruption marker on
        // a corrupted index.
        final Map<Class<? extends ElasticsearchException>, Integer> exceptions = new HashMap<>();
        exceptions.put(org.elasticsearch.index.snapshots.IndexShardSnapshotFailedException.class, 0);
        exceptions.put(org.elasticsearch.search.dfs.DfsPhaseExecutionException.class, 1);
        exceptions.put(org.elasticsearch.common.util.CancellableThreads.ExecutionCancelledException.class, 2);
        exceptions.put(org.elasticsearch.discovery.MasterNotDiscoveredException.class, 3);
        exceptions.put(org.elasticsearch.ElasticsearchSecurityException.class, 4);
        exceptions.put(org.elasticsearch.index.snapshots.IndexShardRestoreException.class, 5);
        exceptions.put(org.elasticsearch.indices.IndexClosedException.class, 6);
        exceptions.put(org.elasticsearch.http.BindHttpException.class, 7);
        exceptions.put(org.elasticsearch.action.search.ReduceSearchPhaseException.class, 8);
        exceptions.put(org.elasticsearch.node.NodeClosedException.class, 9);
        exceptions.put(org.elasticsearch.index.engine.SnapshotFailedEngineException.class, 10);
        exceptions.put(org.elasticsearch.index.shard.ShardNotFoundException.class, 11);
        exceptions.put(org.elasticsearch.transport.ConnectTransportException.class, 12);
        exceptions.put(org.elasticsearch.transport.NotSerializableTransportException.class, 13);
        exceptions.put(org.elasticsearch.transport.ResponseHandlerFailureTransportException.class, 14);
        exceptions.put(org.elasticsearch.indices.IndexCreationException.class, 15);
        exceptions.put(org.elasticsearch.index.IndexNotFoundException.class, 16);
        exceptions.put(org.elasticsearch.cluster.routing.IllegalShardRoutingStateException.class, 17);
        exceptions.put(org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException.class, 18);
        exceptions.put(org.elasticsearch.ResourceNotFoundException.class, 19);
        exceptions.put(org.elasticsearch.transport.ActionTransportException.class, 20);
        exceptions.put(org.elasticsearch.ElasticsearchGenerationException.class, 21);
        exceptions.put(org.elasticsearch.index.engine.CreateFailedEngineException.class, 22);
        exceptions.put(org.elasticsearch.index.shard.IndexShardStartedException.class, 23);
        exceptions.put(org.elasticsearch.search.SearchContextMissingException.class, 24);
        exceptions.put(org.elasticsearch.script.ScriptException.class, 25);
        exceptions.put(org.elasticsearch.index.shard.TranslogRecoveryPerformer.BatchOperationException.class, 26);
        exceptions.put(org.elasticsearch.snapshots.SnapshotCreationException.class, 27);
        exceptions.put(org.elasticsearch.index.engine.DeleteFailedEngineException.class, 28);
        exceptions.put(org.elasticsearch.index.engine.DocumentMissingException.class, 29);
        exceptions.put(org.elasticsearch.snapshots.SnapshotException.class, 30);
        exceptions.put(org.elasticsearch.indices.InvalidAliasNameException.class, 31);
        exceptions.put(org.elasticsearch.indices.InvalidIndexNameException.class, 32);
        exceptions.put(org.elasticsearch.indices.IndexPrimaryShardNotAllocatedException.class, 33);
        exceptions.put(org.elasticsearch.transport.TransportException.class, 34);
        exceptions.put(org.elasticsearch.ElasticsearchParseException.class, 35);
        exceptions.put(org.elasticsearch.search.SearchException.class, 36);
        exceptions.put(org.elasticsearch.index.mapper.MapperException.class, 37);
        exceptions.put(org.elasticsearch.indices.InvalidTypeNameException.class, 38);
        exceptions.put(org.elasticsearch.snapshots.SnapshotRestoreException.class, 39);
        exceptions.put(org.elasticsearch.index.query.QueryParsingException.class, 40);
        exceptions.put(org.elasticsearch.index.shard.IndexShardClosedException.class, 41);
        exceptions.put(org.elasticsearch.indices.recovery.RecoverFilesRecoveryException.class, 42);
        exceptions.put(org.elasticsearch.index.translog.TruncatedTranslogException.class, 43);
        exceptions.put(org.elasticsearch.indices.recovery.RecoveryFailedException.class, 44);
        exceptions.put(org.elasticsearch.index.shard.IndexShardRelocatedException.class, 45);
        exceptions.put(org.elasticsearch.transport.NodeShouldNotConnectException.class, 46);
        exceptions.put(org.elasticsearch.indices.IndexTemplateAlreadyExistsException.class, 47);
        exceptions.put(org.elasticsearch.index.translog.TranslogCorruptedException.class, 48);
        exceptions.put(org.elasticsearch.cluster.block.ClusterBlockException.class, 49);
        exceptions.put(org.elasticsearch.search.fetch.FetchPhaseExecutionException.class, 50);
        exceptions.put(org.elasticsearch.index.IndexShardAlreadyExistsException.class, 51);
        exceptions.put(org.elasticsearch.index.engine.VersionConflictEngineException.class, 52);
        exceptions.put(org.elasticsearch.index.engine.EngineException.class, 53);
        exceptions.put(org.elasticsearch.index.engine.DocumentAlreadyExistsException.class, 54);
        exceptions.put(org.elasticsearch.action.NoSuchNodeException.class, 55);
        exceptions.put(org.elasticsearch.common.settings.SettingsException.class, 56);
        exceptions.put(org.elasticsearch.indices.IndexTemplateMissingException.class, 57);
        exceptions.put(org.elasticsearch.transport.SendRequestTransportException.class, 58);
        exceptions.put(org.elasticsearch.common.util.concurrent.EsRejectedExecutionException.class, 59);
        exceptions.put(org.elasticsearch.common.lucene.Lucene.EarlyTerminationException.class, 60);
        exceptions.put(org.elasticsearch.cluster.routing.RoutingValidationException.class, 61);
        exceptions.put(org.elasticsearch.common.io.stream.NotSerializableExceptionWrapper.class, 62);
        exceptions.put(org.elasticsearch.indices.AliasFilterParsingException.class, 63);
        exceptions.put(org.elasticsearch.index.engine.DeleteByQueryFailedEngineException.class, 64);
        exceptions.put(org.elasticsearch.gateway.GatewayException.class, 65);
        exceptions.put(org.elasticsearch.index.shard.IndexShardNotRecoveringException.class, 66);
        exceptions.put(org.elasticsearch.http.HttpException.class, 67);
        exceptions.put(org.elasticsearch.ElasticsearchException.class, 68);
        exceptions.put(org.elasticsearch.snapshots.SnapshotMissingException.class, 69);
        exceptions.put(org.elasticsearch.action.PrimaryMissingActionException.class, 70);
        exceptions.put(org.elasticsearch.action.FailedNodeException.class, 71);
        exceptions.put(org.elasticsearch.search.SearchParseException.class, 72);
        exceptions.put(org.elasticsearch.snapshots.ConcurrentSnapshotExecutionException.class, 73);
        exceptions.put(org.elasticsearch.common.blobstore.BlobStoreException.class, 74);
        exceptions.put(org.elasticsearch.cluster.IncompatibleClusterStateVersionException.class, 75);
        exceptions.put(org.elasticsearch.index.engine.RecoveryEngineException.class, 76);
        exceptions.put(org.elasticsearch.common.util.concurrent.UncategorizedExecutionException.class, 77);
        exceptions.put(org.elasticsearch.action.TimestampParsingException.class, 78);
        exceptions.put(org.elasticsearch.action.RoutingMissingException.class, 79);
        exceptions.put(org.elasticsearch.index.engine.IndexFailedEngineException.class, 80);
        exceptions.put(org.elasticsearch.index.snapshots.IndexShardRestoreFailedException.class, 81);
        exceptions.put(org.elasticsearch.repositories.RepositoryException.class, 82);
        exceptions.put(org.elasticsearch.transport.ReceiveTimeoutTransportException.class, 83);
        exceptions.put(org.elasticsearch.transport.NodeDisconnectedException.class, 84);
        exceptions.put(org.elasticsearch.index.AlreadyExpiredException.class, 85);
        exceptions.put(org.elasticsearch.search.aggregations.AggregationExecutionException.class, 86);
        exceptions.put(org.elasticsearch.index.mapper.MergeMappingException.class, 87);
        exceptions.put(org.elasticsearch.indices.InvalidIndexTemplateException.class, 88);
        exceptions.put(org.elasticsearch.percolator.PercolateException.class, 89);
        exceptions.put(org.elasticsearch.index.engine.RefreshFailedEngineException.class, 90);
        exceptions.put(org.elasticsearch.search.aggregations.AggregationInitializationException.class, 91);
        exceptions.put(org.elasticsearch.indices.recovery.DelayRecoveryException.class, 92);
        exceptions.put(org.elasticsearch.search.warmer.IndexWarmerMissingException.class, 93);
        exceptions.put(org.elasticsearch.client.transport.NoNodeAvailableException.class, 94);
        exceptions.put(org.elasticsearch.script.groovy.GroovyScriptCompilationException.class, 95);
        exceptions.put(org.elasticsearch.snapshots.InvalidSnapshotNameException.class, 96);
        exceptions.put(org.elasticsearch.index.shard.IllegalIndexShardStateException.class, 97);
        exceptions.put(org.elasticsearch.index.snapshots.IndexShardSnapshotException.class, 98);
        exceptions.put(org.elasticsearch.index.shard.IndexShardNotStartedException.class, 99);
        exceptions.put(org.elasticsearch.action.search.SearchPhaseExecutionException.class, 100);
        exceptions.put(org.elasticsearch.transport.ActionNotFoundTransportException.class, 101);
        exceptions.put(org.elasticsearch.transport.TransportSerializationException.class, 102);
        exceptions.put(org.elasticsearch.transport.RemoteTransportException.class, 103);
        exceptions.put(org.elasticsearch.index.engine.EngineCreationFailureException.class, 104);
        exceptions.put(org.elasticsearch.cluster.routing.RoutingException.class, 105);
        exceptions.put(org.elasticsearch.index.shard.IndexShardRecoveryException.class, 106);
        exceptions.put(org.elasticsearch.repositories.RepositoryMissingException.class, 107);
        exceptions.put(org.elasticsearch.index.percolator.PercolatorException.class, 108);
        exceptions.put(org.elasticsearch.index.engine.DocumentSourceMissingException.class, 109);
        exceptions.put(org.elasticsearch.index.engine.FlushNotAllowedEngineException.class, 110);
        exceptions.put(org.elasticsearch.common.settings.NoClassSettingsException.class, 111);
        exceptions.put(org.elasticsearch.transport.BindTransportException.class, 112);
        exceptions.put(org.elasticsearch.rest.action.admin.indices.alias.delete.AliasesNotFoundException.class, 113);
        exceptions.put(org.elasticsearch.index.shard.IndexShardRecoveringException.class, 114);
        exceptions.put(org.elasticsearch.index.translog.TranslogException.class, 115);
        exceptions.put(org.elasticsearch.cluster.metadata.ProcessClusterEventTimeoutException.class, 116);
        exceptions.put(org.elasticsearch.action.support.replication.TransportReplicationAction.RetryOnPrimaryException.class, 117);
        exceptions.put(org.elasticsearch.ElasticsearchTimeoutException.class, 118);
        exceptions.put(org.elasticsearch.search.query.QueryPhaseExecutionException.class, 119);
        exceptions.put(org.elasticsearch.repositories.RepositoryVerificationException.class, 120);
        exceptions.put(org.elasticsearch.search.aggregations.InvalidAggregationPathException.class, 121);
        exceptions.put(org.elasticsearch.script.groovy.GroovyScriptExecutionException.class, 122);
        exceptions.put(org.elasticsearch.indices.IndexAlreadyExistsException.class, 123);
        exceptions.put(org.elasticsearch.script.Script.ScriptParseException.class, 124);
        exceptions.put(org.elasticsearch.transport.netty.SizeHeaderFrameDecoder.HttpOnTransportException.class, 125);
        exceptions.put(org.elasticsearch.index.mapper.MapperParsingException.class, 126);
        exceptions.put(org.elasticsearch.search.SearchContextException.class, 127);
        exceptions.put(org.elasticsearch.search.builder.SearchSourceBuilderException.class, 128);
        exceptions.put(org.elasticsearch.index.engine.EngineClosedException.class, 129);
        exceptions.put(org.elasticsearch.action.NoShardAvailableActionException.class, 130);
        exceptions.put(org.elasticsearch.action.UnavailableShardsException.class, 131);
        exceptions.put(org.elasticsearch.index.engine.FlushFailedEngineException.class, 132);
        exceptions.put(org.elasticsearch.common.breaker.CircuitBreakingException.class, 133);
        exceptions.put(org.elasticsearch.transport.NodeNotConnectedException.class, 134);
        exceptions.put(org.elasticsearch.index.mapper.StrictDynamicMappingException.class, 135);
        exceptions.put(org.elasticsearch.action.support.replication.TransportReplicationAction.RetryOnReplicaException.class, 136);
        exceptions.put(org.elasticsearch.indices.TypeMissingException.class, 137);
        exceptions.put(org.elasticsearch.script.expression.ExpressionScriptCompilationException.class, 138);
        exceptions.put(org.elasticsearch.script.expression.ExpressionScriptExecutionException.class, 139);

        Map<Integer, Constructor<? extends ElasticsearchException>> idToSupplier = new HashMap<>();
        for (Map.Entry<Class<? extends ElasticsearchException>, Integer> e : exceptions.entrySet()) {
            try {
                Constructor<? extends ElasticsearchException> constructor = e.getKey().getDeclaredConstructor(StreamInput.class);
                if (constructor == null) {
                    throw new IllegalStateException(e.getKey().getName() + " has not StreamInput ctor");
                }
                assert e.getValue().intValue() >= 0;
                if (idToSupplier.get(e.getValue().intValue()) != null) {
                    throw new IllegalStateException("ordinal [" + e.getValue().intValue()  +"] is used more than once");
                }
                idToSupplier.put(e.getValue().intValue(), constructor);
            } catch (NoSuchMethodException t) {
                throw new RuntimeException("failed to register [" + e.getKey().getName() + "] exception must have a public StreamInput ctor", t);
            }
        }

        ID_TO_SUPPLIER = Collections.unmodifiableMap(idToSupplier);
        CLASS_TO_ID = Collections.unmodifiableMap(exceptions);
    }

    public String getIndex() {
        List<String> index = getHeader(INDEX_HEADER_KEY);
        if (index != null && index.isEmpty() == false) {
            return index.get(0);
        }

        return null;
    }

    public ShardId getShardId() {
        List<String> shard = getHeader(SHARD_HEADER_KEY);
        if (shard != null && shard.isEmpty() == false) {
            return new ShardId(getIndex(), Integer.parseInt(shard.get(0)));
        }
        return null;
    }

    public void setIndex(Index index) {
        if (index != null) {
            addHeader(INDEX_HEADER_KEY, index.getName());
        }
    }

    public void setIndex(String index) {
        if (index != null) {
            addHeader(INDEX_HEADER_KEY, index);
        }
    }

    public void setShard(ShardId shardId) {
        if (shardId != null) {
            addHeader(INDEX_HEADER_KEY, shardId.getIndex());
            addHeader(SHARD_HEADER_KEY, Integer.toString(shardId.id()));
        }
    }

    public void setResources(String type, String... id) {
        assert type != null;
        addHeader(RESOURCE_HEADER_ID_KEY, id);
        addHeader(RESOURCE_HEADER_TYPE_KEY, type);
    }

    public List<String> getResourceId() {
        return getHeader(RESOURCE_HEADER_ID_KEY);
    }

    public String getResourceType() {
        List<String> header = getHeader(RESOURCE_HEADER_TYPE_KEY);
        if (header != null && header.isEmpty() == false) {
            assert header.size() == 1;
            return header.get(0);
        }
        return null;
    }

    public static void renderThrowable(XContentBuilder builder, Params params, Throwable t) throws IOException {
        builder.startObject("error");
        final ElasticsearchException[] rootCauses = ElasticsearchException.guessRootCauses(t);
        builder.field("root_cause");
        builder.startArray();
        for (ElasticsearchException rootCause : rootCauses){
            builder.startObject();
            rootCause.toXContent(builder, new ToXContent.DelegatingMapParams(Collections.singletonMap(ElasticsearchException.REST_EXCEPTION_SKIP_CAUSE, "true"), params));
            builder.endObject();
        }
        builder.endArray();
        ElasticsearchException.toXContent(builder, params, t);
        builder.endObject();
    }
}