ElasticsearchException.java 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  1. /*
  2. * Licensed to Elasticsearch under one or more contributor
  3. * license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright
  5. * ownership. Elasticsearch licenses this file to you under
  6. * the Apache License, Version 2.0 (the "License"); you may
  7. * not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. package org.elasticsearch;
  20. import org.elasticsearch.action.support.replication.ReplicationOperation;
  21. import org.elasticsearch.cluster.action.shard.ShardStateAction;
  22. import org.elasticsearch.common.CheckedFunction;
  23. import org.elasticsearch.common.Nullable;
  24. import org.elasticsearch.common.ParseField;
  25. import org.elasticsearch.common.collect.Tuple;
  26. import org.elasticsearch.common.io.stream.StreamInput;
  27. import org.elasticsearch.common.io.stream.StreamOutput;
  28. import org.elasticsearch.common.io.stream.Writeable;
  29. import org.elasticsearch.common.logging.LoggerMessageFormat;
  30. import org.elasticsearch.common.xcontent.ToXContentFragment;
  31. import org.elasticsearch.common.xcontent.XContentBuilder;
  32. import org.elasticsearch.common.xcontent.XContentParseException;
  33. import org.elasticsearch.common.xcontent.XContentParser;
  34. import org.elasticsearch.index.Index;
  35. import org.elasticsearch.index.shard.ShardId;
  36. import org.elasticsearch.rest.RestStatus;
  37. import org.elasticsearch.search.SearchException;
  38. import org.elasticsearch.search.aggregations.MultiBucketConsumerService;
  39. import org.elasticsearch.transport.TcpTransport;
  40. import java.io.IOException;
  41. import java.util.ArrayList;
  42. import java.util.Arrays;
  43. import java.util.Collections;
  44. import java.util.HashMap;
  45. import java.util.List;
  46. import java.util.Map;
  47. import java.util.Set;
  48. import java.util.stream.Collectors;
  49. import static java.util.Collections.emptyMap;
  50. import static java.util.Collections.singletonMap;
  51. import static java.util.Collections.unmodifiableMap;
  52. import static org.elasticsearch.cluster.metadata.IndexMetadata.INDEX_UUID_NA_VALUE;
  53. import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
  54. import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
  55. /**
  56. * A base class for all elasticsearch exceptions.
  57. */
  58. public class ElasticsearchException extends RuntimeException implements ToXContentFragment, Writeable {
  59. private static final Version UNKNOWN_VERSION_ADDED = Version.fromId(0);
  60. /**
  61. * Passed in the {@link Params} of {@link #generateThrowableXContent(XContentBuilder, Params, Throwable)}
  62. * to control if the {@code caused_by} element should render. Unlike most parameters to {@code toXContent} methods this parameter is
  63. * internal only and not available as a URL parameter.
  64. */
  65. private static final String REST_EXCEPTION_SKIP_CAUSE = "rest.exception.cause.skip";
  66. /**
  67. * Passed in the {@link Params} of {@link #generateThrowableXContent(XContentBuilder, Params, Throwable)}
  68. * to control if the {@code stack_trace} element should render. Unlike most parameters to {@code toXContent} methods this parameter is
  69. * internal only and not available as a URL parameter. Use the {@code error_trace} parameter instead.
  70. */
  71. public static final String REST_EXCEPTION_SKIP_STACK_TRACE = "rest.exception.stacktrace.skip";
  72. public static final boolean REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT = true;
  73. private static final boolean REST_EXCEPTION_SKIP_CAUSE_DEFAULT = false;
  74. private static final String INDEX_METADATA_KEY = "es.index";
  75. private static final String INDEX_METADATA_KEY_UUID = "es.index_uuid";
  76. private static final String SHARD_METADATA_KEY = "es.shard";
  77. private static final String RESOURCE_METADATA_TYPE_KEY = "es.resource.type";
  78. private static final String RESOURCE_METADATA_ID_KEY = "es.resource.id";
  79. private static final String TYPE = "type";
  80. private static final String REASON = "reason";
  81. private static final String CAUSED_BY = "caused_by";
  82. private static final ParseField SUPPRESSED = new ParseField("suppressed");
  83. public static final String STACK_TRACE = "stack_trace";
  84. private static final String HEADER = "header";
  85. private static final String ERROR = "error";
  86. private static final String ROOT_CAUSE = "root_cause";
  87. private static final Map<Integer, CheckedFunction<StreamInput, ? extends ElasticsearchException, IOException>> ID_TO_SUPPLIER;
  88. private static final Map<Class<? extends ElasticsearchException>, ElasticsearchExceptionHandle> CLASS_TO_ELASTICSEARCH_EXCEPTION_HANDLE;
  89. private final Map<String, List<String>> metadata = new HashMap<>();
  90. private final Map<String, List<String>> headers = new HashMap<>();
  91. /**
  92. * Construct a <code>ElasticsearchException</code> with the specified cause exception.
  93. */
  94. public ElasticsearchException(Throwable cause) {
  95. super(cause);
  96. }
  97. /**
  98. * Construct a <code>ElasticsearchException</code> with the specified detail message.
  99. *
  100. * The message can be parameterized using <code>{}</code> as placeholders for the given
  101. * arguments
  102. *
  103. * @param msg the detail message
  104. * @param args the arguments for the message
  105. */
  106. public ElasticsearchException(String msg, Object... args) {
  107. super(LoggerMessageFormat.format(msg, args));
  108. }
  109. /**
  110. * Construct a <code>ElasticsearchException</code> with the specified detail message
  111. * and nested exception.
  112. *
  113. * The message can be parameterized using <code>{}</code> as placeholders for the given
  114. * arguments
  115. *
  116. * @param msg the detail message
  117. * @param cause the nested exception
  118. * @param args the arguments for the message
  119. */
  120. public ElasticsearchException(String msg, Throwable cause, Object... args) {
  121. super(LoggerMessageFormat.format(msg, args), cause);
  122. }
  123. public ElasticsearchException(StreamInput in) throws IOException {
  124. super(in.readOptionalString(), in.readException());
  125. readStackTrace(this, in);
  126. headers.putAll(in.readMapOfLists(StreamInput::readString, StreamInput::readString));
  127. metadata.putAll(in.readMapOfLists(StreamInput::readString, StreamInput::readString));
  128. }
  129. /**
  130. * Adds a new piece of metadata with the given key.
  131. * If the provided key is already present, the corresponding metadata will be replaced
  132. */
  133. public void addMetadata(String key, String... values) {
  134. addMetadata(key, Arrays.asList(values));
  135. }
  136. /**
  137. * Adds a new piece of metadata with the given key.
  138. * If the provided key is already present, the corresponding metadata will be replaced
  139. */
  140. public void addMetadata(String key, List<String> values) {
  141. //we need to enforce this otherwise bw comp doesn't work properly, as "es." was the previous criteria to split headers in two sets
  142. if (key.startsWith("es.") == false) {
  143. throw new IllegalArgumentException("exception metadata must start with [es.], found [" + key + "] instead");
  144. }
  145. this.metadata.put(key, values);
  146. }
  147. /**
  148. * Returns a set of all metadata keys on this exception
  149. */
  150. public Set<String> getMetadataKeys() {
  151. return metadata.keySet();
  152. }
  153. /**
  154. * Returns the list of metadata values for the given key or {@code null} if no metadata for the
  155. * given key exists.
  156. */
  157. public List<String> getMetadata(String key) {
  158. return metadata.get(key);
  159. }
  160. protected Map<String, List<String>> getMetadata() {
  161. return metadata;
  162. }
  163. /**
  164. * Adds a new header with the given key.
  165. * This method will replace existing header if a header with the same key already exists
  166. */
  167. public void addHeader(String key, List<String> value) {
  168. //we need to enforce this otherwise bw comp doesn't work properly, as "es." was the previous criteria to split headers in two sets
  169. if (key.startsWith("es.")) {
  170. throw new IllegalArgumentException("exception headers must not start with [es.], found [" + key + "] instead");
  171. }
  172. this.headers.put(key, value);
  173. }
  174. /**
  175. * Adds a new header with the given key.
  176. * This method will replace existing header if a header with the same key already exists
  177. */
  178. public void addHeader(String key, String... value) {
  179. addHeader(key, Arrays.asList(value));
  180. }
  181. /**
  182. * Returns a set of all header keys on this exception
  183. */
  184. public Set<String> getHeaderKeys() {
  185. return headers.keySet();
  186. }
  187. /**
  188. * Returns the list of header values for the given key or {@code null} if no header for the
  189. * given key exists.
  190. */
  191. public List<String> getHeader(String key) {
  192. return headers.get(key);
  193. }
  194. protected Map<String, List<String>> getHeaders() {
  195. return headers;
  196. }
  197. /**
  198. * Returns the rest status code associated with this exception.
  199. */
  200. public RestStatus status() {
  201. Throwable cause = unwrapCause();
  202. if (cause == this) {
  203. return RestStatus.INTERNAL_SERVER_ERROR;
  204. } else {
  205. return ExceptionsHelper.status(cause);
  206. }
  207. }
  208. /**
  209. * Unwraps the actual cause from the exception for cases when the exception is a
  210. * {@link ElasticsearchWrapperException}.
  211. *
  212. * @see ExceptionsHelper#unwrapCause(Throwable)
  213. */
  214. public Throwable unwrapCause() {
  215. return ExceptionsHelper.unwrapCause(this);
  216. }
  217. /**
  218. * Return the detail message, including the message from the nested exception
  219. * if there is one.
  220. */
  221. public String getDetailedMessage() {
  222. if (getCause() != null) {
  223. StringBuilder sb = new StringBuilder();
  224. sb.append(toString()).append("; ");
  225. if (getCause() instanceof ElasticsearchException) {
  226. sb.append(((ElasticsearchException) getCause()).getDetailedMessage());
  227. } else {
  228. sb.append(getCause());
  229. }
  230. return sb.toString();
  231. } else {
  232. return super.toString();
  233. }
  234. }
  235. /**
  236. * Retrieve the innermost cause of this exception, if none, returns the current exception.
  237. */
  238. public Throwable getRootCause() {
  239. Throwable rootCause = this;
  240. Throwable cause = getCause();
  241. while (cause != null && cause != rootCause) {
  242. rootCause = cause;
  243. cause = cause.getCause();
  244. }
  245. return rootCause;
  246. }
  247. @Override
  248. public void writeTo(StreamOutput out) throws IOException {
  249. out.writeOptionalString(this.getMessage());
  250. out.writeException(this.getCause());
  251. writeStackTraces(this, out, StreamOutput::writeException);
  252. out.writeMapOfLists(headers, StreamOutput::writeString, StreamOutput::writeString);
  253. out.writeMapOfLists(metadata, StreamOutput::writeString, StreamOutput::writeString);
  254. }
  255. public static ElasticsearchException readException(StreamInput input, int id) throws IOException {
  256. CheckedFunction<StreamInput, ? extends ElasticsearchException, IOException> elasticsearchException = ID_TO_SUPPLIER.get(id);
  257. if (elasticsearchException == null) {
  258. if (id == 127 && input.getVersion().before(Version.V_7_5_0)) {
  259. // was SearchContextException
  260. return new SearchException(input);
  261. }
  262. throw new IllegalStateException("unknown exception for id: " + id);
  263. }
  264. return elasticsearchException.apply(input);
  265. }
  266. /**
  267. * Returns <code>true</code> iff the given class is a registered for an exception to be read.
  268. */
  269. public static boolean isRegistered(Class<? extends Throwable> exception, Version version) {
  270. ElasticsearchExceptionHandle elasticsearchExceptionHandle = CLASS_TO_ELASTICSEARCH_EXCEPTION_HANDLE.get(exception);
  271. if (elasticsearchExceptionHandle != null) {
  272. return version.onOrAfter(elasticsearchExceptionHandle.versionAdded);
  273. }
  274. return false;
  275. }
  276. static Set<Class<? extends ElasticsearchException>> getRegisteredKeys() { // for testing
  277. return CLASS_TO_ELASTICSEARCH_EXCEPTION_HANDLE.keySet();
  278. }
  279. /**
  280. * Returns the serialization id the given exception.
  281. */
  282. public static int getId(Class<? extends ElasticsearchException> exception) {
  283. return CLASS_TO_ELASTICSEARCH_EXCEPTION_HANDLE.get(exception).id;
  284. }
  285. @Override
  286. public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
  287. Throwable ex = ExceptionsHelper.unwrapCause(this);
  288. if (ex != this) {
  289. generateThrowableXContent(builder, params, this);
  290. } else {
  291. innerToXContent(builder, params, this, getExceptionName(), getMessage(), headers, metadata, getCause());
  292. }
  293. return builder;
  294. }
  295. protected static void innerToXContent(XContentBuilder builder, Params params,
  296. Throwable throwable, String type, String message, Map<String, List<String>> headers,
  297. Map<String, List<String>> metadata, Throwable cause) throws IOException {
  298. builder.field(TYPE, type);
  299. builder.field(REASON, message);
  300. for (Map.Entry<String, List<String>> entry : metadata.entrySet()) {
  301. headerToXContent(builder, entry.getKey().substring("es.".length()), entry.getValue());
  302. }
  303. if (throwable instanceof ElasticsearchException) {
  304. ElasticsearchException exception = (ElasticsearchException) throwable;
  305. exception.metadataToXContent(builder, params);
  306. }
  307. if (params.paramAsBoolean(REST_EXCEPTION_SKIP_CAUSE, REST_EXCEPTION_SKIP_CAUSE_DEFAULT) == false) {
  308. if (cause != null) {
  309. builder.field(CAUSED_BY);
  310. builder.startObject();
  311. generateThrowableXContent(builder, params, cause);
  312. builder.endObject();
  313. }
  314. }
  315. if (headers.isEmpty() == false) {
  316. builder.startObject(HEADER);
  317. for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
  318. headerToXContent(builder, entry.getKey(), entry.getValue());
  319. }
  320. builder.endObject();
  321. }
  322. if (params.paramAsBoolean(REST_EXCEPTION_SKIP_STACK_TRACE, REST_EXCEPTION_SKIP_STACK_TRACE_DEFAULT) == false) {
  323. builder.field(STACK_TRACE, ExceptionsHelper.stackTrace(throwable));
  324. }
  325. Throwable[] allSuppressed = throwable.getSuppressed();
  326. if (allSuppressed.length > 0) {
  327. builder.startArray(SUPPRESSED.getPreferredName());
  328. for (Throwable suppressed : allSuppressed) {
  329. builder.startObject();
  330. generateThrowableXContent(builder, params, suppressed);
  331. builder.endObject();
  332. }
  333. builder.endArray();
  334. }
  335. }
  336. private static void headerToXContent(XContentBuilder builder, String key, List<String> values) throws IOException {
  337. if (values != null && values.isEmpty() == false) {
  338. if (values.size() == 1) {
  339. builder.field(key, values.get(0));
  340. } else {
  341. builder.startArray(key);
  342. for (String value : values) {
  343. builder.value(value);
  344. }
  345. builder.endArray();
  346. }
  347. }
  348. }
  349. /**
  350. * Renders additional per exception information into the XContent
  351. */
  352. protected void metadataToXContent(XContentBuilder builder, Params params) throws IOException {
  353. }
  354. /**
  355. * Generate a {@link ElasticsearchException} from a {@link XContentParser}. This does not
  356. * return the original exception type (ie NodeClosedException for example) but just wraps
  357. * the type, the reason and the cause of the exception. It also recursively parses the
  358. * tree structure of the cause, returning it as a tree structure of {@link ElasticsearchException}
  359. * instances.
  360. */
  361. public static ElasticsearchException fromXContent(XContentParser parser) throws IOException {
  362. XContentParser.Token token = parser.nextToken();
  363. ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
  364. return innerFromXContent(parser, false);
  365. }
  366. public static ElasticsearchException innerFromXContent(XContentParser parser, boolean parseRootCauses) throws IOException {
  367. XContentParser.Token token = parser.currentToken();
  368. ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
  369. String type = null, reason = null, stack = null;
  370. ElasticsearchException cause = null;
  371. Map<String, List<String>> metadata = new HashMap<>();
  372. Map<String, List<String>> headers = new HashMap<>();
  373. List<ElasticsearchException> rootCauses = new ArrayList<>();
  374. List<ElasticsearchException> suppressed = new ArrayList<>();
  375. for (; token == XContentParser.Token.FIELD_NAME; token = parser.nextToken()) {
  376. String currentFieldName = parser.currentName();
  377. token = parser.nextToken();
  378. if (token.isValue()) {
  379. if (TYPE.equals(currentFieldName)) {
  380. type = parser.text();
  381. } else if (REASON.equals(currentFieldName)) {
  382. reason = parser.text();
  383. } else if (STACK_TRACE.equals(currentFieldName)) {
  384. stack = parser.text();
  385. } else if (token == XContentParser.Token.VALUE_STRING) {
  386. metadata.put(currentFieldName, Collections.singletonList(parser.text()));
  387. }
  388. } else if (token == XContentParser.Token.START_OBJECT) {
  389. if (CAUSED_BY.equals(currentFieldName)) {
  390. cause = fromXContent(parser);
  391. } else if (HEADER.equals(currentFieldName)) {
  392. while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
  393. if (token == XContentParser.Token.FIELD_NAME) {
  394. currentFieldName = parser.currentName();
  395. } else {
  396. List<String> values = headers.getOrDefault(currentFieldName, new ArrayList<>());
  397. if (token == XContentParser.Token.VALUE_STRING) {
  398. values.add(parser.text());
  399. } else if (token == XContentParser.Token.START_ARRAY) {
  400. while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
  401. if (token == XContentParser.Token.VALUE_STRING) {
  402. values.add(parser.text());
  403. } else {
  404. parser.skipChildren();
  405. }
  406. }
  407. } else if (token == XContentParser.Token.START_OBJECT) {
  408. parser.skipChildren();
  409. }
  410. headers.put(currentFieldName, values);
  411. }
  412. }
  413. } else {
  414. // Any additional metadata object added by the metadataToXContent method is ignored
  415. // and skipped, so that the parser does not fail on unknown fields. The parser only
  416. // support metadata key-pairs and metadata arrays of values.
  417. parser.skipChildren();
  418. }
  419. } else if (token == XContentParser.Token.START_ARRAY) {
  420. if (parseRootCauses && ROOT_CAUSE.equals(currentFieldName)) {
  421. while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
  422. rootCauses.add(fromXContent(parser));
  423. }
  424. } else if (SUPPRESSED.match(currentFieldName, parser.getDeprecationHandler())) {
  425. while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
  426. suppressed.add(fromXContent(parser));
  427. }
  428. } else {
  429. // Parse the array and add each item to the corresponding list of metadata.
  430. // Arrays of objects are not supported yet and just ignored and skipped.
  431. List<String> values = new ArrayList<>();
  432. while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
  433. if (token == XContentParser.Token.VALUE_STRING) {
  434. values.add(parser.text());
  435. } else {
  436. parser.skipChildren();
  437. }
  438. }
  439. if (values.size() > 0) {
  440. if (metadata.containsKey(currentFieldName)) {
  441. values.addAll(metadata.get(currentFieldName));
  442. }
  443. metadata.put(currentFieldName, values);
  444. }
  445. }
  446. }
  447. }
  448. ElasticsearchException e = new ElasticsearchException(buildMessage(type, reason, stack), cause);
  449. for (Map.Entry<String, List<String>> entry : metadata.entrySet()) {
  450. //subclasses can print out additional metadata through the metadataToXContent method. Simple key-value pairs will be
  451. //parsed back and become part of this metadata set, while objects and arrays are not supported when parsing back.
  452. //Those key-value pairs become part of the metadata set and inherit the "es." prefix as that is currently required
  453. //by addMetadata. The prefix will get stripped out when printing metadata out so it will be effectively invisible.
  454. //TODO move subclasses that print out simple metadata to using addMetadata directly and support also numbers and booleans.
  455. //TODO rename metadataToXContent and have only SearchPhaseExecutionException use it, which prints out complex objects
  456. e.addMetadata("es." + entry.getKey(), entry.getValue());
  457. }
  458. for (Map.Entry<String, List<String>> header : headers.entrySet()) {
  459. e.addHeader(header.getKey(), header.getValue());
  460. }
  461. // Adds root causes as suppressed exception. This way they are not lost
  462. // after parsing and can be retrieved using getSuppressed() method.
  463. for (ElasticsearchException rootCause : rootCauses) {
  464. e.addSuppressed(rootCause);
  465. }
  466. for (ElasticsearchException s : suppressed) {
  467. e.addSuppressed(s);
  468. }
  469. return e;
  470. }
  471. /**
  472. * Static toXContent helper method that renders {@link org.elasticsearch.ElasticsearchException} or {@link Throwable} instances
  473. * as XContent, delegating the rendering to {@link #toXContent(XContentBuilder, Params)}
  474. * or {@link #innerToXContent(XContentBuilder, Params, Throwable, String, String, Map, Map, Throwable)}.
  475. *
  476. * This method is usually used when the {@link Throwable} is rendered as a part of another XContent object, and its result can
  477. * be parsed back using the {@link #fromXContent(XContentParser)} method.
  478. */
  479. public static void generateThrowableXContent(XContentBuilder builder, Params params, Throwable t) throws IOException {
  480. t = ExceptionsHelper.unwrapCause(t);
  481. if (t instanceof ElasticsearchException) {
  482. ((ElasticsearchException) t).toXContent(builder, params);
  483. } else {
  484. innerToXContent(builder, params, t, getExceptionName(t), t.getMessage(), emptyMap(), emptyMap(), t.getCause());
  485. }
  486. }
  487. /**
  488. * Render any exception as a xcontent, encapsulated within a field or object named "error". The level of details that are rendered
  489. * depends on the value of the "detailed" parameter: when it's false only a simple message based on the type and message of the
  490. * exception is rendered. When it's true all detail are provided including guesses root causes, cause and potentially stack
  491. * trace.
  492. *
  493. * This method is usually used when the {@link Exception} is rendered as a full XContent object, and its output can be parsed
  494. * by the {@link #failureFromXContent(XContentParser)} method.
  495. */
  496. public static void generateFailureXContent(XContentBuilder builder, Params params, @Nullable Exception e, boolean detailed)
  497. throws IOException {
  498. // No exception to render as an error
  499. if (e == null) {
  500. builder.field(ERROR, "unknown");
  501. return;
  502. }
  503. // Render the exception with a simple message
  504. if (detailed == false) {
  505. String message = "No ElasticsearchException found";
  506. Throwable t = e;
  507. for (int counter = 0; counter < 10 && t != null; counter++) {
  508. if (t instanceof ElasticsearchException) {
  509. message = t.getClass().getSimpleName() + "[" + t.getMessage() + "]";
  510. break;
  511. }
  512. t = t.getCause();
  513. }
  514. builder.field(ERROR, message);
  515. return;
  516. }
  517. // Render the exception with all details
  518. final ElasticsearchException[] rootCauses = ElasticsearchException.guessRootCauses(e);
  519. builder.startObject(ERROR);
  520. {
  521. builder.startArray(ROOT_CAUSE);
  522. for (ElasticsearchException rootCause : rootCauses) {
  523. builder.startObject();
  524. rootCause.toXContent(builder, new DelegatingMapParams(singletonMap(REST_EXCEPTION_SKIP_CAUSE, "true"), params));
  525. builder.endObject();
  526. }
  527. builder.endArray();
  528. }
  529. generateThrowableXContent(builder, params, e);
  530. builder.endObject();
  531. }
  532. /**
  533. * Parses the output of {@link #generateFailureXContent(XContentBuilder, Params, Exception, boolean)}
  534. */
  535. public static ElasticsearchException failureFromXContent(XContentParser parser) throws IOException {
  536. XContentParser.Token token = parser.currentToken();
  537. ensureFieldName(parser, token, ERROR);
  538. token = parser.nextToken();
  539. if (token.isValue()) {
  540. return new ElasticsearchException(buildMessage("exception", parser.text(), null));
  541. }
  542. ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation);
  543. token = parser.nextToken();
  544. // Root causes are parsed in the innerFromXContent() and are added as suppressed exceptions.
  545. return innerFromXContent(parser, true);
  546. }
  547. /**
  548. * Returns the root cause of this exception or multiple if different shards caused different exceptions
  549. */
  550. public ElasticsearchException[] guessRootCauses() {
  551. final Throwable cause = getCause();
  552. if (cause != null && cause instanceof ElasticsearchException) {
  553. return ((ElasticsearchException) cause).guessRootCauses();
  554. }
  555. return new ElasticsearchException[]{this};
  556. }
  557. /**
  558. * Returns the root cause of this exception or multiple if different shards caused different exceptions.
  559. * If the given exception is not an instance of {@link org.elasticsearch.ElasticsearchException} an empty array
  560. * is returned.
  561. */
  562. public static ElasticsearchException[] guessRootCauses(Throwable t) {
  563. Throwable ex = ExceptionsHelper.unwrapCause(t);
  564. if (ex instanceof ElasticsearchException) {
  565. // ElasticsearchException knows how to guess its own root cause
  566. return ((ElasticsearchException) ex).guessRootCauses();
  567. }
  568. if (ex instanceof XContentParseException) {
  569. /*
  570. * We'd like to unwrap parsing exceptions to the inner-most
  571. * parsing exception because that is generally the most interesting
  572. * exception to return to the user. If that exception is caused by
  573. * an ElasticsearchException we'd like to keep unwrapping because
  574. * ElasticserachExceptions tend to contain useful information for
  575. * the user.
  576. */
  577. Throwable cause = ex.getCause();
  578. if (cause != null) {
  579. if (cause instanceof XContentParseException || cause instanceof ElasticsearchException) {
  580. return guessRootCauses(ex.getCause());
  581. }
  582. }
  583. }
  584. return new ElasticsearchException[]{new ElasticsearchException(ex.getMessage(), ex) {
  585. @Override
  586. protected String getExceptionName() {
  587. return getExceptionName(getCause());
  588. }
  589. }};
  590. }
  591. protected String getExceptionName() {
  592. return getExceptionName(this);
  593. }
  594. /**
  595. * Returns a underscore case name for the given exception. This method strips {@code Elasticsearch} prefixes from exception names.
  596. */
  597. public static String getExceptionName(Throwable ex) {
  598. String simpleName = ex.getClass().getSimpleName();
  599. if (simpleName.startsWith("Elasticsearch")) {
  600. simpleName = simpleName.substring("Elasticsearch".length());
  601. }
  602. // TODO: do we really need to make the exception name in underscore casing?
  603. return toUnderscoreCase(simpleName);
  604. }
  605. static String buildMessage(String type, String reason, String stack) {
  606. StringBuilder message = new StringBuilder("Elasticsearch exception [");
  607. message.append(TYPE).append('=').append(type).append(", ");
  608. message.append(REASON).append('=').append(reason);
  609. if (stack != null) {
  610. message.append(", ").append(STACK_TRACE).append('=').append(stack);
  611. }
  612. message.append(']');
  613. return message.toString();
  614. }
  615. @Override
  616. public String toString() {
  617. StringBuilder builder = new StringBuilder();
  618. if (metadata.containsKey(INDEX_METADATA_KEY)) {
  619. builder.append(getIndex());
  620. if (metadata.containsKey(SHARD_METADATA_KEY)) {
  621. builder.append('[').append(getShardId()).append(']');
  622. }
  623. builder.append(' ');
  624. }
  625. return builder.append(super.toString().trim()).toString();
  626. }
  627. /**
  628. * Deserializes stacktrace elements as well as suppressed exceptions from the given output stream and
  629. * adds it to the given exception.
  630. */
  631. public static <T extends Throwable> T readStackTrace(T throwable, StreamInput in) throws IOException {
  632. final int stackTraceElements = in.readVInt();
  633. StackTraceElement[] stackTrace = new StackTraceElement[stackTraceElements];
  634. for (int i = 0; i < stackTraceElements; i++) {
  635. final String declaringClasss = in.readString();
  636. final String fileName = in.readOptionalString();
  637. final String methodName = in.readString();
  638. final int lineNumber = in.readVInt();
  639. stackTrace[i] = new StackTraceElement(declaringClasss, methodName, fileName, lineNumber);
  640. }
  641. throwable.setStackTrace(stackTrace);
  642. int numSuppressed = in.readVInt();
  643. for (int i = 0; i < numSuppressed; i++) {
  644. throwable.addSuppressed(in.readException());
  645. }
  646. return throwable;
  647. }
  648. /**
  649. * Serializes the given exceptions stacktrace elements as well as it's suppressed exceptions to the given output stream.
  650. */
  651. public static <T extends Throwable> T writeStackTraces(T throwable, StreamOutput out,
  652. Writer<Throwable> exceptionWriter) throws IOException {
  653. StackTraceElement[] stackTrace = throwable.getStackTrace();
  654. out.writeVInt(stackTrace.length);
  655. for (StackTraceElement element : stackTrace) {
  656. out.writeString(element.getClassName());
  657. out.writeOptionalString(element.getFileName());
  658. out.writeString(element.getMethodName());
  659. out.writeVInt(element.getLineNumber());
  660. }
  661. Throwable[] suppressed = throwable.getSuppressed();
  662. out.writeVInt(suppressed.length);
  663. for (Throwable t : suppressed) {
  664. exceptionWriter.write(out, t);
  665. }
  666. return throwable;
  667. }
  668. /**
  669. * This is the list of Exceptions Elasticsearch can throw over the wire or save into a corruption marker. Each value in the enum is a
  670. * single exception tying the Class to an id for use of the encode side and the id back to a constructor for use on the decode side. As
  671. * such its ok if the exceptions to change names so long as their constructor can still read the exception. Each exception is listed
  672. * in id order below. If you want to remove an exception leave a tombstone comment and mark the id as null in
  673. * ExceptionSerializationTests.testIds.ids.
  674. */
  675. private enum ElasticsearchExceptionHandle {
  676. INDEX_SHARD_SNAPSHOT_FAILED_EXCEPTION(org.elasticsearch.index.snapshots.IndexShardSnapshotFailedException.class,
  677. org.elasticsearch.index.snapshots.IndexShardSnapshotFailedException::new, 0, UNKNOWN_VERSION_ADDED),
  678. DFS_PHASE_EXECUTION_EXCEPTION(org.elasticsearch.search.dfs.DfsPhaseExecutionException.class,
  679. org.elasticsearch.search.dfs.DfsPhaseExecutionException::new, 1, UNKNOWN_VERSION_ADDED),
  680. EXECUTION_CANCELLED_EXCEPTION(org.elasticsearch.common.util.CancellableThreads.ExecutionCancelledException.class,
  681. org.elasticsearch.common.util.CancellableThreads.ExecutionCancelledException::new, 2, UNKNOWN_VERSION_ADDED),
  682. MASTER_NOT_DISCOVERED_EXCEPTION(org.elasticsearch.discovery.MasterNotDiscoveredException.class,
  683. org.elasticsearch.discovery.MasterNotDiscoveredException::new, 3, UNKNOWN_VERSION_ADDED),
  684. ELASTICSEARCH_SECURITY_EXCEPTION(org.elasticsearch.ElasticsearchSecurityException.class,
  685. org.elasticsearch.ElasticsearchSecurityException::new, 4, UNKNOWN_VERSION_ADDED),
  686. INDEX_SHARD_RESTORE_EXCEPTION(org.elasticsearch.index.snapshots.IndexShardRestoreException.class,
  687. org.elasticsearch.index.snapshots.IndexShardRestoreException::new, 5, UNKNOWN_VERSION_ADDED),
  688. INDEX_CLOSED_EXCEPTION(org.elasticsearch.indices.IndexClosedException.class,
  689. org.elasticsearch.indices.IndexClosedException::new, 6, UNKNOWN_VERSION_ADDED),
  690. BIND_HTTP_EXCEPTION(org.elasticsearch.http.BindHttpException.class,
  691. org.elasticsearch.http.BindHttpException::new, 7, UNKNOWN_VERSION_ADDED),
  692. REDUCE_SEARCH_PHASE_EXCEPTION(org.elasticsearch.action.search.ReduceSearchPhaseException.class,
  693. org.elasticsearch.action.search.ReduceSearchPhaseException::new, 8, UNKNOWN_VERSION_ADDED),
  694. NODE_CLOSED_EXCEPTION(org.elasticsearch.node.NodeClosedException.class,
  695. org.elasticsearch.node.NodeClosedException::new, 9, UNKNOWN_VERSION_ADDED),
  696. SNAPSHOT_FAILED_ENGINE_EXCEPTION(org.elasticsearch.index.engine.SnapshotFailedEngineException.class,
  697. org.elasticsearch.index.engine.SnapshotFailedEngineException::new, 10, UNKNOWN_VERSION_ADDED),
  698. SHARD_NOT_FOUND_EXCEPTION(org.elasticsearch.index.shard.ShardNotFoundException.class,
  699. org.elasticsearch.index.shard.ShardNotFoundException::new, 11, UNKNOWN_VERSION_ADDED),
  700. CONNECT_TRANSPORT_EXCEPTION(org.elasticsearch.transport.ConnectTransportException.class,
  701. org.elasticsearch.transport.ConnectTransportException::new, 12, UNKNOWN_VERSION_ADDED),
  702. NOT_SERIALIZABLE_TRANSPORT_EXCEPTION(org.elasticsearch.transport.NotSerializableTransportException.class,
  703. org.elasticsearch.transport.NotSerializableTransportException::new, 13, UNKNOWN_VERSION_ADDED),
  704. RESPONSE_HANDLER_FAILURE_TRANSPORT_EXCEPTION(org.elasticsearch.transport.ResponseHandlerFailureTransportException.class,
  705. org.elasticsearch.transport.ResponseHandlerFailureTransportException::new, 14, UNKNOWN_VERSION_ADDED),
  706. INDEX_CREATION_EXCEPTION(org.elasticsearch.indices.IndexCreationException.class,
  707. org.elasticsearch.indices.IndexCreationException::new, 15, UNKNOWN_VERSION_ADDED),
  708. INDEX_NOT_FOUND_EXCEPTION(org.elasticsearch.index.IndexNotFoundException.class,
  709. org.elasticsearch.index.IndexNotFoundException::new, 16, UNKNOWN_VERSION_ADDED),
  710. ILLEGAL_SHARD_ROUTING_STATE_EXCEPTION(org.elasticsearch.cluster.routing.IllegalShardRoutingStateException.class,
  711. org.elasticsearch.cluster.routing.IllegalShardRoutingStateException::new, 17, UNKNOWN_VERSION_ADDED),
  712. BROADCAST_SHARD_OPERATION_FAILED_EXCEPTION(org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException.class,
  713. org.elasticsearch.action.support.broadcast.BroadcastShardOperationFailedException::new, 18, UNKNOWN_VERSION_ADDED),
  714. RESOURCE_NOT_FOUND_EXCEPTION(org.elasticsearch.ResourceNotFoundException.class,
  715. org.elasticsearch.ResourceNotFoundException::new, 19, UNKNOWN_VERSION_ADDED),
  716. ACTION_TRANSPORT_EXCEPTION(org.elasticsearch.transport.ActionTransportException.class,
  717. org.elasticsearch.transport.ActionTransportException::new, 20, UNKNOWN_VERSION_ADDED),
  718. ELASTICSEARCH_GENERATION_EXCEPTION(org.elasticsearch.ElasticsearchGenerationException.class,
  719. org.elasticsearch.ElasticsearchGenerationException::new, 21, UNKNOWN_VERSION_ADDED),
  720. // 22 was CreateFailedEngineException
  721. INDEX_SHARD_STARTED_EXCEPTION(org.elasticsearch.index.shard.IndexShardStartedException.class,
  722. org.elasticsearch.index.shard.IndexShardStartedException::new, 23, UNKNOWN_VERSION_ADDED),
  723. SEARCH_CONTEXT_MISSING_EXCEPTION(org.elasticsearch.search.SearchContextMissingException.class,
  724. org.elasticsearch.search.SearchContextMissingException::new, 24, UNKNOWN_VERSION_ADDED),
  725. GENERAL_SCRIPT_EXCEPTION(org.elasticsearch.script.GeneralScriptException.class,
  726. org.elasticsearch.script.GeneralScriptException::new, 25, UNKNOWN_VERSION_ADDED),
  727. // 26 was BatchOperationException
  728. SNAPSHOT_CREATION_EXCEPTION(org.elasticsearch.snapshots.SnapshotCreationException.class,
  729. org.elasticsearch.snapshots.SnapshotCreationException::new, 27, UNKNOWN_VERSION_ADDED),
  730. // 28 was DeleteFailedEngineException, deprecated in 6.0, removed in 7.0
  731. DOCUMENT_MISSING_EXCEPTION(org.elasticsearch.index.engine.DocumentMissingException.class,
  732. org.elasticsearch.index.engine.DocumentMissingException::new, 29, UNKNOWN_VERSION_ADDED),
  733. SNAPSHOT_EXCEPTION(org.elasticsearch.snapshots.SnapshotException.class,
  734. org.elasticsearch.snapshots.SnapshotException::new, 30, UNKNOWN_VERSION_ADDED),
  735. INVALID_ALIAS_NAME_EXCEPTION(org.elasticsearch.indices.InvalidAliasNameException.class,
  736. org.elasticsearch.indices.InvalidAliasNameException::new, 31, UNKNOWN_VERSION_ADDED),
  737. INVALID_INDEX_NAME_EXCEPTION(org.elasticsearch.indices.InvalidIndexNameException.class,
  738. org.elasticsearch.indices.InvalidIndexNameException::new, 32, UNKNOWN_VERSION_ADDED),
  739. INDEX_PRIMARY_SHARD_NOT_ALLOCATED_EXCEPTION(org.elasticsearch.indices.IndexPrimaryShardNotAllocatedException.class,
  740. org.elasticsearch.indices.IndexPrimaryShardNotAllocatedException::new, 33, UNKNOWN_VERSION_ADDED),
  741. TRANSPORT_EXCEPTION(org.elasticsearch.transport.TransportException.class,
  742. org.elasticsearch.transport.TransportException::new, 34, UNKNOWN_VERSION_ADDED),
  743. ELASTICSEARCH_PARSE_EXCEPTION(org.elasticsearch.ElasticsearchParseException.class,
  744. org.elasticsearch.ElasticsearchParseException::new, 35, UNKNOWN_VERSION_ADDED),
  745. SEARCH_EXCEPTION(org.elasticsearch.search.SearchException.class,
  746. org.elasticsearch.search.SearchException::new, 36, UNKNOWN_VERSION_ADDED),
  747. MAPPER_EXCEPTION(org.elasticsearch.index.mapper.MapperException.class,
  748. org.elasticsearch.index.mapper.MapperException::new, 37, UNKNOWN_VERSION_ADDED),
  749. INVALID_TYPE_NAME_EXCEPTION(org.elasticsearch.indices.InvalidTypeNameException.class,
  750. org.elasticsearch.indices.InvalidTypeNameException::new, 38, UNKNOWN_VERSION_ADDED),
  751. SNAPSHOT_RESTORE_EXCEPTION(org.elasticsearch.snapshots.SnapshotRestoreException.class,
  752. org.elasticsearch.snapshots.SnapshotRestoreException::new, 39, UNKNOWN_VERSION_ADDED),
  753. PARSING_EXCEPTION(org.elasticsearch.common.ParsingException.class, org.elasticsearch.common.ParsingException::new, 40,
  754. UNKNOWN_VERSION_ADDED),
  755. INDEX_SHARD_CLOSED_EXCEPTION(org.elasticsearch.index.shard.IndexShardClosedException.class,
  756. org.elasticsearch.index.shard.IndexShardClosedException::new, 41, UNKNOWN_VERSION_ADDED),
  757. RECOVER_FILES_RECOVERY_EXCEPTION(org.elasticsearch.indices.recovery.RecoverFilesRecoveryException.class,
  758. org.elasticsearch.indices.recovery.RecoverFilesRecoveryException::new, 42, UNKNOWN_VERSION_ADDED),
  759. TRUNCATED_TRANSLOG_EXCEPTION(org.elasticsearch.index.translog.TruncatedTranslogException.class,
  760. org.elasticsearch.index.translog.TruncatedTranslogException::new, 43, UNKNOWN_VERSION_ADDED),
  761. RECOVERY_FAILED_EXCEPTION(org.elasticsearch.indices.recovery.RecoveryFailedException.class,
  762. org.elasticsearch.indices.recovery.RecoveryFailedException::new, 44, UNKNOWN_VERSION_ADDED),
  763. INDEX_SHARD_RELOCATED_EXCEPTION(org.elasticsearch.index.shard.IndexShardRelocatedException.class,
  764. org.elasticsearch.index.shard.IndexShardRelocatedException::new, 45, UNKNOWN_VERSION_ADDED),
  765. NODE_SHOULD_NOT_CONNECT_EXCEPTION(org.elasticsearch.transport.NodeShouldNotConnectException.class,
  766. org.elasticsearch.transport.NodeShouldNotConnectException::new, 46, UNKNOWN_VERSION_ADDED),
  767. // 47 used to be for IndexTemplateAlreadyExistsException which was deprecated in 5.1 removed in 6.0
  768. TRANSLOG_CORRUPTED_EXCEPTION(org.elasticsearch.index.translog.TranslogCorruptedException.class,
  769. org.elasticsearch.index.translog.TranslogCorruptedException::new, 48, UNKNOWN_VERSION_ADDED),
  770. CLUSTER_BLOCK_EXCEPTION(org.elasticsearch.cluster.block.ClusterBlockException.class,
  771. org.elasticsearch.cluster.block.ClusterBlockException::new, 49, UNKNOWN_VERSION_ADDED),
  772. FETCH_PHASE_EXECUTION_EXCEPTION(org.elasticsearch.search.fetch.FetchPhaseExecutionException.class,
  773. org.elasticsearch.search.fetch.FetchPhaseExecutionException::new, 50, UNKNOWN_VERSION_ADDED),
  774. // 51 used to be for IndexShardAlreadyExistsException which was deprecated in 5.1 removed in 6.0
  775. VERSION_CONFLICT_ENGINE_EXCEPTION(org.elasticsearch.index.engine.VersionConflictEngineException.class,
  776. org.elasticsearch.index.engine.VersionConflictEngineException::new, 52, UNKNOWN_VERSION_ADDED),
  777. ENGINE_EXCEPTION(org.elasticsearch.index.engine.EngineException.class, org.elasticsearch.index.engine.EngineException::new, 53,
  778. UNKNOWN_VERSION_ADDED),
  779. // 54 was DocumentAlreadyExistsException, which is superseded by VersionConflictEngineException
  780. NO_SUCH_NODE_EXCEPTION(org.elasticsearch.action.NoSuchNodeException.class, org.elasticsearch.action.NoSuchNodeException::new, 55,
  781. UNKNOWN_VERSION_ADDED),
  782. SETTINGS_EXCEPTION(org.elasticsearch.common.settings.SettingsException.class,
  783. org.elasticsearch.common.settings.SettingsException::new, 56, UNKNOWN_VERSION_ADDED),
  784. INDEX_TEMPLATE_MISSING_EXCEPTION(org.elasticsearch.indices.IndexTemplateMissingException.class,
  785. org.elasticsearch.indices.IndexTemplateMissingException::new, 57, UNKNOWN_VERSION_ADDED),
  786. SEND_REQUEST_TRANSPORT_EXCEPTION(org.elasticsearch.transport.SendRequestTransportException.class,
  787. org.elasticsearch.transport.SendRequestTransportException::new, 58, UNKNOWN_VERSION_ADDED),
  788. // 59 used to be EsRejectedExecutionException
  789. // 60 used to be for EarlyTerminationException
  790. // 61 used to be for RoutingValidationException
  791. NOT_SERIALIZABLE_EXCEPTION_WRAPPER(org.elasticsearch.common.io.stream.NotSerializableExceptionWrapper.class,
  792. org.elasticsearch.common.io.stream.NotSerializableExceptionWrapper::new, 62, UNKNOWN_VERSION_ADDED),
  793. ALIAS_FILTER_PARSING_EXCEPTION(org.elasticsearch.indices.AliasFilterParsingException.class,
  794. org.elasticsearch.indices.AliasFilterParsingException::new, 63, UNKNOWN_VERSION_ADDED),
  795. // 64 was DeleteByQueryFailedEngineException, which was removed in 5.0
  796. GATEWAY_EXCEPTION(org.elasticsearch.gateway.GatewayException.class, org.elasticsearch.gateway.GatewayException::new, 65,
  797. UNKNOWN_VERSION_ADDED),
  798. INDEX_SHARD_NOT_RECOVERING_EXCEPTION(org.elasticsearch.index.shard.IndexShardNotRecoveringException.class,
  799. org.elasticsearch.index.shard.IndexShardNotRecoveringException::new, 66, UNKNOWN_VERSION_ADDED),
  800. HTTP_EXCEPTION(org.elasticsearch.http.HttpException.class, org.elasticsearch.http.HttpException::new, 67, UNKNOWN_VERSION_ADDED),
  801. ELASTICSEARCH_EXCEPTION(org.elasticsearch.ElasticsearchException.class,
  802. org.elasticsearch.ElasticsearchException::new, 68, UNKNOWN_VERSION_ADDED),
  803. SNAPSHOT_MISSING_EXCEPTION(org.elasticsearch.snapshots.SnapshotMissingException.class,
  804. org.elasticsearch.snapshots.SnapshotMissingException::new, 69, UNKNOWN_VERSION_ADDED),
  805. PRIMARY_MISSING_ACTION_EXCEPTION(org.elasticsearch.action.PrimaryMissingActionException.class,
  806. org.elasticsearch.action.PrimaryMissingActionException::new, 70, UNKNOWN_VERSION_ADDED),
  807. FAILED_NODE_EXCEPTION(org.elasticsearch.action.FailedNodeException.class, org.elasticsearch.action.FailedNodeException::new, 71,
  808. UNKNOWN_VERSION_ADDED),
  809. SEARCH_PARSE_EXCEPTION(org.elasticsearch.search.SearchParseException.class, org.elasticsearch.search.SearchParseException::new, 72,
  810. UNKNOWN_VERSION_ADDED),
  811. CONCURRENT_SNAPSHOT_EXECUTION_EXCEPTION(org.elasticsearch.snapshots.ConcurrentSnapshotExecutionException.class,
  812. org.elasticsearch.snapshots.ConcurrentSnapshotExecutionException::new, 73, UNKNOWN_VERSION_ADDED),
  813. BLOB_STORE_EXCEPTION(org.elasticsearch.common.blobstore.BlobStoreException.class,
  814. org.elasticsearch.common.blobstore.BlobStoreException::new, 74, UNKNOWN_VERSION_ADDED),
  815. INCOMPATIBLE_CLUSTER_STATE_VERSION_EXCEPTION(org.elasticsearch.cluster.IncompatibleClusterStateVersionException.class,
  816. org.elasticsearch.cluster.IncompatibleClusterStateVersionException::new, 75, UNKNOWN_VERSION_ADDED),
  817. RECOVERY_ENGINE_EXCEPTION(org.elasticsearch.index.engine.RecoveryEngineException.class,
  818. org.elasticsearch.index.engine.RecoveryEngineException::new, 76, UNKNOWN_VERSION_ADDED),
  819. UNCATEGORIZED_EXECUTION_EXCEPTION(org.elasticsearch.common.util.concurrent.UncategorizedExecutionException.class,
  820. org.elasticsearch.common.util.concurrent.UncategorizedExecutionException::new, 77, UNKNOWN_VERSION_ADDED),
  821. TIMESTAMP_PARSING_EXCEPTION(org.elasticsearch.action.TimestampParsingException.class,
  822. org.elasticsearch.action.TimestampParsingException::new, 78, UNKNOWN_VERSION_ADDED),
  823. ROUTING_MISSING_EXCEPTION(org.elasticsearch.action.RoutingMissingException.class,
  824. org.elasticsearch.action.RoutingMissingException::new, 79, UNKNOWN_VERSION_ADDED),
  825. // 80 was IndexFailedEngineException, deprecated in 6.0, removed in 7.0
  826. INDEX_SHARD_RESTORE_FAILED_EXCEPTION(org.elasticsearch.index.snapshots.IndexShardRestoreFailedException.class,
  827. org.elasticsearch.index.snapshots.IndexShardRestoreFailedException::new, 81, UNKNOWN_VERSION_ADDED),
  828. REPOSITORY_EXCEPTION(org.elasticsearch.repositories.RepositoryException.class,
  829. org.elasticsearch.repositories.RepositoryException::new, 82, UNKNOWN_VERSION_ADDED),
  830. RECEIVE_TIMEOUT_TRANSPORT_EXCEPTION(org.elasticsearch.transport.ReceiveTimeoutTransportException.class,
  831. org.elasticsearch.transport.ReceiveTimeoutTransportException::new, 83, UNKNOWN_VERSION_ADDED),
  832. NODE_DISCONNECTED_EXCEPTION(org.elasticsearch.transport.NodeDisconnectedException.class,
  833. org.elasticsearch.transport.NodeDisconnectedException::new, 84, UNKNOWN_VERSION_ADDED),
  834. // 85 used to be for AlreadyExpiredException
  835. AGGREGATION_EXECUTION_EXCEPTION(org.elasticsearch.search.aggregations.AggregationExecutionException.class,
  836. org.elasticsearch.search.aggregations.AggregationExecutionException::new, 86, UNKNOWN_VERSION_ADDED),
  837. // 87 used to be for MergeMappingException
  838. INVALID_INDEX_TEMPLATE_EXCEPTION(org.elasticsearch.indices.InvalidIndexTemplateException.class,
  839. org.elasticsearch.indices.InvalidIndexTemplateException::new, 88, UNKNOWN_VERSION_ADDED),
  840. REFRESH_FAILED_ENGINE_EXCEPTION(org.elasticsearch.index.engine.RefreshFailedEngineException.class,
  841. org.elasticsearch.index.engine.RefreshFailedEngineException::new, 90, UNKNOWN_VERSION_ADDED),
  842. AGGREGATION_INITIALIZATION_EXCEPTION(org.elasticsearch.search.aggregations.AggregationInitializationException.class,
  843. org.elasticsearch.search.aggregations.AggregationInitializationException::new, 91, UNKNOWN_VERSION_ADDED),
  844. DELAY_RECOVERY_EXCEPTION(org.elasticsearch.indices.recovery.DelayRecoveryException.class,
  845. org.elasticsearch.indices.recovery.DelayRecoveryException::new, 92, UNKNOWN_VERSION_ADDED),
  846. // 93 used to be for IndexWarmerMissingException
  847. NO_NODE_AVAILABLE_EXCEPTION(org.elasticsearch.client.transport.NoNodeAvailableException.class,
  848. org.elasticsearch.client.transport.NoNodeAvailableException::new, 94, UNKNOWN_VERSION_ADDED),
  849. INVALID_SNAPSHOT_NAME_EXCEPTION(org.elasticsearch.snapshots.InvalidSnapshotNameException.class,
  850. org.elasticsearch.snapshots.InvalidSnapshotNameException::new, 96, UNKNOWN_VERSION_ADDED),
  851. ILLEGAL_INDEX_SHARD_STATE_EXCEPTION(org.elasticsearch.index.shard.IllegalIndexShardStateException.class,
  852. org.elasticsearch.index.shard.IllegalIndexShardStateException::new, 97, UNKNOWN_VERSION_ADDED),
  853. INDEX_SHARD_SNAPSHOT_EXCEPTION(org.elasticsearch.index.snapshots.IndexShardSnapshotException.class,
  854. org.elasticsearch.index.snapshots.IndexShardSnapshotException::new, 98, UNKNOWN_VERSION_ADDED),
  855. INDEX_SHARD_NOT_STARTED_EXCEPTION(org.elasticsearch.index.shard.IndexShardNotStartedException.class,
  856. org.elasticsearch.index.shard.IndexShardNotStartedException::new, 99, UNKNOWN_VERSION_ADDED),
  857. SEARCH_PHASE_EXECUTION_EXCEPTION(org.elasticsearch.action.search.SearchPhaseExecutionException.class,
  858. org.elasticsearch.action.search.SearchPhaseExecutionException::new, 100, UNKNOWN_VERSION_ADDED),
  859. ACTION_NOT_FOUND_TRANSPORT_EXCEPTION(org.elasticsearch.transport.ActionNotFoundTransportException.class,
  860. org.elasticsearch.transport.ActionNotFoundTransportException::new, 101, UNKNOWN_VERSION_ADDED),
  861. TRANSPORT_SERIALIZATION_EXCEPTION(org.elasticsearch.transport.TransportSerializationException.class,
  862. org.elasticsearch.transport.TransportSerializationException::new, 102, UNKNOWN_VERSION_ADDED),
  863. REMOTE_TRANSPORT_EXCEPTION(org.elasticsearch.transport.RemoteTransportException.class,
  864. org.elasticsearch.transport.RemoteTransportException::new, 103, UNKNOWN_VERSION_ADDED),
  865. ENGINE_CREATION_FAILURE_EXCEPTION(org.elasticsearch.index.engine.EngineCreationFailureException.class,
  866. org.elasticsearch.index.engine.EngineCreationFailureException::new, 104, UNKNOWN_VERSION_ADDED),
  867. ROUTING_EXCEPTION(org.elasticsearch.cluster.routing.RoutingException.class,
  868. org.elasticsearch.cluster.routing.RoutingException::new, 105, UNKNOWN_VERSION_ADDED),
  869. INDEX_SHARD_RECOVERY_EXCEPTION(org.elasticsearch.index.shard.IndexShardRecoveryException.class,
  870. org.elasticsearch.index.shard.IndexShardRecoveryException::new, 106, UNKNOWN_VERSION_ADDED),
  871. REPOSITORY_MISSING_EXCEPTION(org.elasticsearch.repositories.RepositoryMissingException.class,
  872. org.elasticsearch.repositories.RepositoryMissingException::new, 107, UNKNOWN_VERSION_ADDED),
  873. DOCUMENT_SOURCE_MISSING_EXCEPTION(org.elasticsearch.index.engine.DocumentSourceMissingException.class,
  874. org.elasticsearch.index.engine.DocumentSourceMissingException::new, 109, UNKNOWN_VERSION_ADDED),
  875. // 110 used to be FlushNotAllowedEngineException
  876. NO_CLASS_SETTINGS_EXCEPTION(org.elasticsearch.common.settings.NoClassSettingsException.class,
  877. org.elasticsearch.common.settings.NoClassSettingsException::new, 111, UNKNOWN_VERSION_ADDED),
  878. BIND_TRANSPORT_EXCEPTION(org.elasticsearch.transport.BindTransportException.class,
  879. org.elasticsearch.transport.BindTransportException::new, 112, UNKNOWN_VERSION_ADDED),
  880. ALIASES_NOT_FOUND_EXCEPTION(org.elasticsearch.rest.action.admin.indices.AliasesNotFoundException.class,
  881. org.elasticsearch.rest.action.admin.indices.AliasesNotFoundException::new, 113, UNKNOWN_VERSION_ADDED),
  882. INDEX_SHARD_RECOVERING_EXCEPTION(org.elasticsearch.index.shard.IndexShardRecoveringException.class,
  883. org.elasticsearch.index.shard.IndexShardRecoveringException::new, 114, UNKNOWN_VERSION_ADDED),
  884. TRANSLOG_EXCEPTION(org.elasticsearch.index.translog.TranslogException.class,
  885. org.elasticsearch.index.translog.TranslogException::new, 115, UNKNOWN_VERSION_ADDED),
  886. PROCESS_CLUSTER_EVENT_TIMEOUT_EXCEPTION(org.elasticsearch.cluster.metadata.ProcessClusterEventTimeoutException.class,
  887. org.elasticsearch.cluster.metadata.ProcessClusterEventTimeoutException::new, 116, UNKNOWN_VERSION_ADDED),
  888. RETRY_ON_PRIMARY_EXCEPTION(ReplicationOperation.RetryOnPrimaryException.class,
  889. ReplicationOperation.RetryOnPrimaryException::new, 117, UNKNOWN_VERSION_ADDED),
  890. ELASTICSEARCH_TIMEOUT_EXCEPTION(org.elasticsearch.ElasticsearchTimeoutException.class,
  891. org.elasticsearch.ElasticsearchTimeoutException::new, 118, UNKNOWN_VERSION_ADDED),
  892. QUERY_PHASE_EXECUTION_EXCEPTION(org.elasticsearch.search.query.QueryPhaseExecutionException.class,
  893. org.elasticsearch.search.query.QueryPhaseExecutionException::new, 119, UNKNOWN_VERSION_ADDED),
  894. REPOSITORY_VERIFICATION_EXCEPTION(org.elasticsearch.repositories.RepositoryVerificationException.class,
  895. org.elasticsearch.repositories.RepositoryVerificationException::new, 120, UNKNOWN_VERSION_ADDED),
  896. INVALID_AGGREGATION_PATH_EXCEPTION(org.elasticsearch.search.aggregations.InvalidAggregationPathException.class,
  897. org.elasticsearch.search.aggregations.InvalidAggregationPathException::new, 121, UNKNOWN_VERSION_ADDED),
  898. // 123 used to be IndexAlreadyExistsException and was renamed
  899. RESOURCE_ALREADY_EXISTS_EXCEPTION(ResourceAlreadyExistsException.class,
  900. ResourceAlreadyExistsException::new, 123, UNKNOWN_VERSION_ADDED),
  901. // 124 used to be Script.ScriptParseException
  902. HTTP_REQUEST_ON_TRANSPORT_EXCEPTION(TcpTransport.HttpRequestOnTransportException.class,
  903. TcpTransport.HttpRequestOnTransportException::new, 125, UNKNOWN_VERSION_ADDED),
  904. MAPPER_PARSING_EXCEPTION(org.elasticsearch.index.mapper.MapperParsingException.class,
  905. org.elasticsearch.index.mapper.MapperParsingException::new, 126, UNKNOWN_VERSION_ADDED),
  906. // 127 used to be org.elasticsearch.search.SearchContextException
  907. SEARCH_SOURCE_BUILDER_EXCEPTION(org.elasticsearch.search.builder.SearchSourceBuilderException.class,
  908. org.elasticsearch.search.builder.SearchSourceBuilderException::new, 128, UNKNOWN_VERSION_ADDED),
  909. // 129 was EngineClosedException
  910. NO_SHARD_AVAILABLE_ACTION_EXCEPTION(org.elasticsearch.action.NoShardAvailableActionException.class,
  911. org.elasticsearch.action.NoShardAvailableActionException::new, 130, UNKNOWN_VERSION_ADDED),
  912. UNAVAILABLE_SHARDS_EXCEPTION(org.elasticsearch.action.UnavailableShardsException.class,
  913. org.elasticsearch.action.UnavailableShardsException::new, 131, UNKNOWN_VERSION_ADDED),
  914. FLUSH_FAILED_ENGINE_EXCEPTION(org.elasticsearch.index.engine.FlushFailedEngineException.class,
  915. org.elasticsearch.index.engine.FlushFailedEngineException::new, 132, UNKNOWN_VERSION_ADDED),
  916. CIRCUIT_BREAKING_EXCEPTION(org.elasticsearch.common.breaker.CircuitBreakingException.class,
  917. org.elasticsearch.common.breaker.CircuitBreakingException::new, 133, UNKNOWN_VERSION_ADDED),
  918. NODE_NOT_CONNECTED_EXCEPTION(org.elasticsearch.transport.NodeNotConnectedException.class,
  919. org.elasticsearch.transport.NodeNotConnectedException::new, 134, UNKNOWN_VERSION_ADDED),
  920. STRICT_DYNAMIC_MAPPING_EXCEPTION(org.elasticsearch.index.mapper.StrictDynamicMappingException.class,
  921. org.elasticsearch.index.mapper.StrictDynamicMappingException::new, 135, UNKNOWN_VERSION_ADDED),
  922. RETRY_ON_REPLICA_EXCEPTION(org.elasticsearch.action.support.replication.TransportReplicationAction.RetryOnReplicaException.class,
  923. org.elasticsearch.action.support.replication.TransportReplicationAction.RetryOnReplicaException::new, 136,
  924. UNKNOWN_VERSION_ADDED),
  925. TYPE_MISSING_EXCEPTION(org.elasticsearch.indices.TypeMissingException.class,
  926. org.elasticsearch.indices.TypeMissingException::new, 137, UNKNOWN_VERSION_ADDED),
  927. FAILED_TO_COMMIT_CLUSTER_STATE_EXCEPTION(org.elasticsearch.cluster.coordination.FailedToCommitClusterStateException.class,
  928. org.elasticsearch.cluster.coordination.FailedToCommitClusterStateException::new, 140, UNKNOWN_VERSION_ADDED),
  929. QUERY_SHARD_EXCEPTION(org.elasticsearch.index.query.QueryShardException.class,
  930. org.elasticsearch.index.query.QueryShardException::new, 141, UNKNOWN_VERSION_ADDED),
  931. NO_LONGER_PRIMARY_SHARD_EXCEPTION(ShardStateAction.NoLongerPrimaryShardException.class,
  932. ShardStateAction.NoLongerPrimaryShardException::new, 142, UNKNOWN_VERSION_ADDED),
  933. SCRIPT_EXCEPTION(org.elasticsearch.script.ScriptException.class, org.elasticsearch.script.ScriptException::new, 143,
  934. UNKNOWN_VERSION_ADDED),
  935. NOT_MASTER_EXCEPTION(org.elasticsearch.cluster.NotMasterException.class, org.elasticsearch.cluster.NotMasterException::new, 144,
  936. UNKNOWN_VERSION_ADDED),
  937. STATUS_EXCEPTION(org.elasticsearch.ElasticsearchStatusException.class, org.elasticsearch.ElasticsearchStatusException::new, 145,
  938. UNKNOWN_VERSION_ADDED),
  939. TASK_CANCELLED_EXCEPTION(org.elasticsearch.tasks.TaskCancelledException.class,
  940. org.elasticsearch.tasks.TaskCancelledException::new, 146, UNKNOWN_VERSION_ADDED),
  941. SHARD_LOCK_OBTAIN_FAILED_EXCEPTION(org.elasticsearch.env.ShardLockObtainFailedException.class,
  942. org.elasticsearch.env.ShardLockObtainFailedException::new, 147, UNKNOWN_VERSION_ADDED),
  943. // 148 was UnknownNamedObjectException
  944. TOO_MANY_BUCKETS_EXCEPTION(MultiBucketConsumerService.TooManyBucketsException.class,
  945. MultiBucketConsumerService.TooManyBucketsException::new, 149, UNKNOWN_VERSION_ADDED),
  946. COORDINATION_STATE_REJECTED_EXCEPTION(org.elasticsearch.cluster.coordination.CoordinationStateRejectedException.class,
  947. org.elasticsearch.cluster.coordination.CoordinationStateRejectedException::new, 150, Version.V_7_0_0),
  948. SNAPSHOT_IN_PROGRESS_EXCEPTION(org.elasticsearch.snapshots.SnapshotInProgressException.class,
  949. org.elasticsearch.snapshots.SnapshotInProgressException::new, 151, UNKNOWN_VERSION_ADDED),
  950. NO_SUCH_REMOTE_CLUSTER_EXCEPTION(org.elasticsearch.transport.NoSuchRemoteClusterException.class,
  951. org.elasticsearch.transport.NoSuchRemoteClusterException::new, 152, UNKNOWN_VERSION_ADDED),
  952. RETENTION_LEASE_ALREADY_EXISTS_EXCEPTION(
  953. org.elasticsearch.index.seqno.RetentionLeaseAlreadyExistsException.class,
  954. org.elasticsearch.index.seqno.RetentionLeaseAlreadyExistsException::new,
  955. 153,
  956. UNKNOWN_VERSION_ADDED),
  957. RETENTION_LEASE_NOT_FOUND_EXCEPTION(
  958. org.elasticsearch.index.seqno.RetentionLeaseNotFoundException.class,
  959. org.elasticsearch.index.seqno.RetentionLeaseNotFoundException::new,
  960. 154,
  961. UNKNOWN_VERSION_ADDED),
  962. SHARD_NOT_IN_PRIMARY_MODE_EXCEPTION(
  963. org.elasticsearch.index.shard.ShardNotInPrimaryModeException.class,
  964. org.elasticsearch.index.shard.ShardNotInPrimaryModeException::new,
  965. 155,
  966. UNKNOWN_VERSION_ADDED),
  967. RETENTION_LEASE_INVALID_RETAINING_SEQUENCE_NUMBER_EXCEPTION(
  968. org.elasticsearch.index.seqno.RetentionLeaseInvalidRetainingSeqNoException.class,
  969. org.elasticsearch.index.seqno.RetentionLeaseInvalidRetainingSeqNoException::new,
  970. 156,
  971. Version.V_7_5_0),
  972. INGEST_PROCESSOR_EXCEPTION(
  973. org.elasticsearch.ingest.IngestProcessorException.class,
  974. org.elasticsearch.ingest.IngestProcessorException::new,
  975. 157,
  976. Version.V_7_5_0),
  977. PEER_RECOVERY_NOT_FOUND_EXCEPTION(
  978. org.elasticsearch.indices.recovery.PeerRecoveryNotFound.class,
  979. org.elasticsearch.indices.recovery.PeerRecoveryNotFound::new,
  980. 158,
  981. Version.V_7_9_0);
  982. final Class<? extends ElasticsearchException> exceptionClass;
  983. final CheckedFunction<StreamInput, ? extends ElasticsearchException, IOException> constructor;
  984. final int id;
  985. final Version versionAdded;
  986. <E extends ElasticsearchException> ElasticsearchExceptionHandle(Class<E> exceptionClass,
  987. CheckedFunction<StreamInput, E, IOException> constructor, int id,
  988. Version versionAdded) {
  989. // We need the exceptionClass because you can't dig it out of the constructor reliably.
  990. this.exceptionClass = exceptionClass;
  991. this.constructor = constructor;
  992. this.versionAdded = versionAdded;
  993. this.id = id;
  994. }
  995. }
  996. /**
  997. * Returns an array of all registered handle IDs. These are the IDs for every registered
  998. * exception.
  999. *
  1000. * @return an array of all registered handle IDs
  1001. */
  1002. static int[] ids() {
  1003. return Arrays.stream(ElasticsearchExceptionHandle.values()).mapToInt(h -> h.id).toArray();
  1004. }
  1005. /**
  1006. * Returns an array of all registered pairs of handle IDs and exception classes. These pairs are
  1007. * provided for every registered exception.
  1008. *
  1009. * @return an array of all registered pairs of handle IDs and exception classes
  1010. */
  1011. static Tuple<Integer, Class<? extends ElasticsearchException>>[] classes() {
  1012. @SuppressWarnings("unchecked")
  1013. final Tuple<Integer, Class<? extends ElasticsearchException>>[] ts =
  1014. Arrays.stream(ElasticsearchExceptionHandle.values())
  1015. .map(h -> Tuple.tuple(h.id, h.exceptionClass)).toArray(Tuple[]::new);
  1016. return ts;
  1017. }
  1018. static {
  1019. ID_TO_SUPPLIER = unmodifiableMap(Arrays
  1020. .stream(ElasticsearchExceptionHandle.values()).collect(Collectors.toMap(e -> e.id, e -> e.constructor)));
  1021. CLASS_TO_ELASTICSEARCH_EXCEPTION_HANDLE = unmodifiableMap(Arrays
  1022. .stream(ElasticsearchExceptionHandle.values()).collect(Collectors.toMap(e -> e.exceptionClass, e -> e)));
  1023. }
  1024. public Index getIndex() {
  1025. List<String> index = getMetadata(INDEX_METADATA_KEY);
  1026. if (index != null && index.isEmpty() == false) {
  1027. List<String> index_uuid = getMetadata(INDEX_METADATA_KEY_UUID);
  1028. return new Index(index.get(0), index_uuid.get(0));
  1029. }
  1030. return null;
  1031. }
  1032. public ShardId getShardId() {
  1033. List<String> shard = getMetadata(SHARD_METADATA_KEY);
  1034. if (shard != null && shard.isEmpty() == false) {
  1035. return new ShardId(getIndex(), Integer.parseInt(shard.get(0)));
  1036. }
  1037. return null;
  1038. }
  1039. public void setIndex(Index index) {
  1040. if (index != null) {
  1041. addMetadata(INDEX_METADATA_KEY, index.getName());
  1042. addMetadata(INDEX_METADATA_KEY_UUID, index.getUUID());
  1043. }
  1044. }
  1045. public void setIndex(String index) {
  1046. if (index != null) {
  1047. setIndex(new Index(index, INDEX_UUID_NA_VALUE));
  1048. }
  1049. }
  1050. public void setShard(ShardId shardId) {
  1051. if (shardId != null) {
  1052. setIndex(shardId.getIndex());
  1053. addMetadata(SHARD_METADATA_KEY, Integer.toString(shardId.id()));
  1054. }
  1055. }
  1056. public void setResources(String type, String... id) {
  1057. assert type != null;
  1058. addMetadata(RESOURCE_METADATA_ID_KEY, id);
  1059. addMetadata(RESOURCE_METADATA_TYPE_KEY, type);
  1060. }
  1061. public List<String> getResourceId() {
  1062. return getMetadata(RESOURCE_METADATA_ID_KEY);
  1063. }
  1064. public String getResourceType() {
  1065. List<String> header = getMetadata(RESOURCE_METADATA_TYPE_KEY);
  1066. if (header != null && header.isEmpty() == false) {
  1067. assert header.size() == 1;
  1068. return header.get(0);
  1069. }
  1070. return null;
  1071. }
  1072. // lower cases and adds underscores to transitions in a name
  1073. private static String toUnderscoreCase(String value) {
  1074. StringBuilder sb = new StringBuilder();
  1075. boolean changed = false;
  1076. for (int i = 0; i < value.length(); i++) {
  1077. char c = value.charAt(i);
  1078. if (Character.isUpperCase(c)) {
  1079. if (!changed) {
  1080. // copy it over here
  1081. for (int j = 0; j < i; j++) {
  1082. sb.append(value.charAt(j));
  1083. }
  1084. changed = true;
  1085. if (i == 0) {
  1086. sb.append(Character.toLowerCase(c));
  1087. } else {
  1088. sb.append('_');
  1089. sb.append(Character.toLowerCase(c));
  1090. }
  1091. } else {
  1092. sb.append('_');
  1093. sb.append(Character.toLowerCase(c));
  1094. }
  1095. } else {
  1096. if (changed) {
  1097. sb.append(c);
  1098. }
  1099. }
  1100. }
  1101. if (!changed) {
  1102. return value;
  1103. }
  1104. return sb.toString();
  1105. }
  1106. }