diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/SearchResourceIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/SearchResourceIT.java index e7156cb03dc9..a3ca03c46c0d 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/SearchResourceIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/SearchResourceIT.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.UUID; import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; @@ -2124,4 +2125,28 @@ void testValidQueryFilterStillApplies() throws Exception { assertEquals(200, response.statusCode(), response.body()); } + + @ParameterizedTest + @ValueSource( + strings = { + "/v1/search/query?q=*&index=nope_search_index", + "/v1/search/query?q=*&index=table_search_index,nope_search_index", + "/v1/search/get/nope_search_index/doc/00000000-0000-0000-0000-000000000000" + }) + void testUnknownIndexIsRejectedAsBadRequest(String path) throws Exception { + HttpResponse response = httpGetSearch(path); + + assertEquals(400, response.statusCode(), path + " returned " + response.body()); + assertTrue( + response.body().contains("Unknown search index [nope_search_index]"), + path + " returned " + response.body()); + } + + @Test + void testMissingDocumentInAKnownIndexIsNotFound() throws Exception { + HttpResponse response = + httpGetSearch("/v1/search/get/table_search_index/doc/" + UUID.randomUUID()); + + assertEquals(404, response.statusCode(), response.body()); + } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchEngineErrors.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchEngineErrors.java index 34363e67ed02..60e4a62b5807 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchEngineErrors.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchEngineErrors.java @@ -3,6 +3,8 @@ import jakarta.ws.rs.core.Response; import java.util.List; import org.openmetadata.sdk.exception.SearchException; +import org.openmetadata.sdk.exception.SearchIndexNotFoundException; +import org.openmetadata.service.Entity; /** * Translates a search engine's own HTTP failure into the status OpenMetadata reports to its caller. @@ -14,6 +16,10 @@ * *

Only {@code 400} is re-mapped. Everything else, including the {@code 429} the engine returns * when a circuit breaker trips, stays a 500: those are genuinely not the caller's doing. + * + *

A missing index ({@code 404}) depends on whose name it is. A name OpenMetadata does not manage + * is the caller's mistake and is a 400. A managed index that is missing from the cluster, for + * example after a failed reindex, is an outage and stays a 500. Issue #31878. */ public final class SearchEngineErrors { @@ -21,6 +27,29 @@ public final class SearchEngineErrors { private SearchEngineErrors() {} + /** Classifies a missing index using the names the running server manages. */ + public static SearchIndexNotFoundException indexNotFound(String resolvedIndex) { + SearchRepository searchRepository = Entity.getSearchRepository(); + List unknownNames = + searchRepository == null ? List.of() : searchRepository.unknownIndexNames(resolvedIndex); + return indexNotFound(resolvedIndex, unknownNames); + } + + /** + * @param resolvedIndex the index expression sent to the engine + * @param unknownNames the names in it that OpenMetadata does not manage, as the caller typed them + */ + public static SearchIndexNotFoundException indexNotFound( + String resolvedIndex, List unknownNames) { + if (unknownNames.isEmpty()) { + return new SearchIndexNotFoundException( + String.format("Failed to find index %s", resolvedIndex)); + } + return SearchIndexNotFoundException.withStatus( + Response.Status.BAD_REQUEST, + String.format("Unknown search index [%s]", String.join(", ", unknownNames))); + } + /** * @param upstreamStatus HTTP status the search engine returned * @param message the engine's own message diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexUtils.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexUtils.java index eceaa39b70d6..dc2965c3205d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexUtils.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexUtils.java @@ -1,5 +1,6 @@ package org.openmetadata.service.search; +import static org.openmetadata.common.utils.CommonUtil.listOrEmpty; import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty; import static org.openmetadata.search.IndexMapping.INDEX_NAME_SEPARATOR; import static org.openmetadata.service.apps.bundles.insights.search.DataInsightsSearchInterface.getStringWithClusterAlias; @@ -20,6 +21,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -140,6 +142,66 @@ private static String resolveSingleAliasToken( return clusterPrefix == null ? token : clusterPrefix + token; } + /** + * Every name, without cluster prefix, that resolves to an index or alias OpenMetadata manages: + * entity types, canonical index names and their aliases. + */ + public static Set knownIndexNames(Map entityIndexMap) { + if (entityIndexMap == null) { + return Set.of(); + } + Set names = new HashSet<>(entityIndexMap.keySet()); + entityIndexMap.values().forEach(mapping -> addMappingNames(names, mapping)); + names.remove(null); + return Set.copyOf(names); + } + + private static void addMappingNames(Set names, IndexMapping mapping) { + names.add(mapping.getIndexName()); + names.add(mapping.getAlias()); + names.addAll(listOrEmpty(mapping.getParentAliases())); + names.addAll(listOrEmpty(mapping.getChildAliases())); + names.addAll(listOrEmpty(mapping.getDataInsightAliases())); + } + + /** + * Names in an index expression, already resolved by {@link #getIndexOrAliasName}, that + * OpenMetadata does not manage, as the caller typed them. Wildcards, exclusions and Data Insights + * names never count as unknown. An empty result means every name is managed, so a missing index + * is a server fault rather than the caller's mistake. + */ + public static List unknownIndexNames( + String resolvedIndex, Set knownIndexNames, String clusterAlias) { + if (nullOrEmpty(resolvedIndex) || nullOrEmpty(knownIndexNames)) { + return List.of(); + } + return Arrays.stream(resolvedIndex.split(",")) + .map(String::trim) + .filter(token -> isPlainIndexName(token, clusterAlias)) + .map(token -> withoutClusterPrefix(token, clusterAlias)) + .filter(name -> !knownIndexNames.contains(name)) + .distinct() + .toList(); + } + + private static boolean isPlainIndexName(String token, String clusterAlias) { + return !token.isEmpty() + && !token.contains("*") + && !token.startsWith("-") + && !token.startsWith(DI_SEARCH_INDEX_PREFIX) + && !token.startsWith(getStringWithClusterAlias(clusterAlias, DI_SEARCH_INDEX_PREFIX)); + } + + private static String withoutClusterPrefix(String token, String clusterAlias) { + if (nullOrEmpty(clusterAlias)) { + return token; + } + String prefix = clusterAlias + INDEX_NAME_SEPARATOR; + return token.startsWith(prefix) && token.length() > prefix.length() + ? token.substring(prefix.length()) + : token; + } + /** * Resolves comma-separated entity names, canonical indexes, and {@code all}/{@code dataAsset} * aliases to sorted, distinct registered entity types. Unknown or empty targets have no members. diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java index a455ffd37fb1..208d233462f3 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java @@ -418,6 +418,7 @@ public static void deferOrRunSearchWrite( @Getter private Map entityIndexMap; private Map aliasIndexMap; + private Set knownIndexNames = Set.of(); /** * Staged index names being populated by an in-flight reindex, keyed by the canonical index name @@ -548,6 +549,7 @@ private void loadIndexMappings() { IndexMappingLoader mappingLoader = IndexMappingLoader.getInstance(); entityIndexMap = mappingLoader.getIndexMapping(); aliasIndexMap = buildAliasIndexMap(entityIndexMap); + knownIndexNames = SearchIndexUtils.knownIndexNames(entityIndexMap); } private static Map buildAliasIndexMap( @@ -1005,6 +1007,11 @@ public String getIndexOrAliasName(String name) { return SearchIndexUtils.getIndexOrAliasName(name, entityIndexMap, aliasIndexMap, clusterAlias); } + /** @see SearchIndexUtils#unknownIndexNames(String, Set, String) */ + public List unknownIndexNames(String resolvedIndex) { + return SearchIndexUtils.unknownIndexNames(resolvedIndex, knownIndexNames, clusterAlias); + } + /** @see SearchIndexUtils#getEntityTypesForIndex(String, Map, Map, String) */ public List getEntityTypesForIndex(String index) { return SearchIndexUtils.getEntityTypesForIndex( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchEntityManager.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchEntityManager.java index 23363e5938f9..f518809e11d1 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchEntityManager.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchEntityManager.java @@ -69,11 +69,11 @@ import org.openmetadata.schema.type.entityRelationship.NodeInformation; import org.openmetadata.schema.utils.JsonUtils; import org.openmetadata.sdk.exception.SearchException; -import org.openmetadata.sdk.exception.SearchIndexNotFoundException; import org.openmetadata.search.IndexMapping; import org.openmetadata.service.Entity; import org.openmetadata.service.search.EntityManagementClient; import org.openmetadata.service.search.SearchClient; +import org.openmetadata.service.search.SearchEngineErrors; import org.openmetadata.service.search.SearchIndexRetryQueue; import org.openmetadata.service.search.SearchIndexUtils; import org.openmetadata.service.search.SearchRetryUtil; @@ -597,12 +597,9 @@ public Response getDocByID(String indexName, String entityId) throws IOException return getResponse(Response.Status.NOT_FOUND, "Document not found."); } + String index = Entity.getSearchRepository().getIndexOrAliasName(indexName); try { - GetResponse response = - client.get( - g -> - g.index(Entity.getSearchRepository().getIndexOrAliasName(indexName)).id(entityId), - Map.class); + GetResponse response = client.get(g -> g.index(index).id(entityId), Map.class); // This path runs no query and has no SubjectContext, so it cannot tell whose restricted // memory a document is: a non-org-wide memory reads as not found rather than leaking. @@ -613,8 +610,7 @@ public Response getDocByID(String indexName, String entityId) throws IOException } } catch (ElasticsearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException( - String.format("Failed to find doc with id %s", entityId)); + throw SearchEngineErrors.indexNotFound(index); } else { throw new SearchException(String.format("Search failed due to %s", e.getMessage())); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchSearchManager.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchSearchManager.java index 18cccfeb570f..469859006b8b 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchSearchManager.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/elasticsearch/ElasticSearchSearchManager.java @@ -68,7 +68,6 @@ import org.openmetadata.schema.type.Include; import org.openmetadata.schema.utils.JsonUtils; import org.openmetadata.sdk.exception.SearchException; -import org.openmetadata.sdk.exception.SearchIndexNotFoundException; import org.openmetadata.service.Entity; import org.openmetadata.service.jdbi3.ListFilter; import org.openmetadata.service.jdbi3.TableRepository; @@ -369,7 +368,7 @@ private SearchResultListMapper doListWithOffset( return new SearchResultListMapper(results, totalHits); } catch (ElasticsearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException(String.format("Failed to find index %s", index)); + throw SearchEngineErrors.indexNotFound(index); } else { throw buildSearchException(e); } @@ -682,7 +681,7 @@ public SearchResultListMapper listWithDeepPagination( results, totalHits, lastHitSortValues, lastDocumentsInBatch); } catch (ElasticsearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException(String.format("Failed to find index %s", index)); + throw SearchEngineErrors.indexNotFound(index); } else { throw buildSearchException(e); } @@ -1146,8 +1145,7 @@ public Response doSearch( } catch (ElasticsearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException( - String.format("Failed to find index %s", request.getIndex())); + throw SearchEngineErrors.indexNotFound(request.getIndex()); } else { throw buildSearchException(e); } @@ -1334,8 +1332,7 @@ public SearchResultListMapper searchForExport( return new SearchResultListMapper(results, totalHits, lastHitSortValues); } catch (ElasticsearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException( - String.format("Failed to find index %s", request.getIndex())); + throw SearchEngineErrors.indexNotFound(request.getIndex()); } else { throw buildSearchException(e); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchEntityManager.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchEntityManager.java index 9e9819b9b024..55b8d8ddddaf 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchEntityManager.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchEntityManager.java @@ -46,11 +46,11 @@ import org.openmetadata.schema.type.entityRelationship.NodeInformation; import org.openmetadata.schema.utils.JsonUtils; import org.openmetadata.sdk.exception.SearchException; -import org.openmetadata.sdk.exception.SearchIndexNotFoundException; import org.openmetadata.search.IndexMapping; import org.openmetadata.service.Entity; import org.openmetadata.service.search.EntityManagementClient; import org.openmetadata.service.search.SearchClient; +import org.openmetadata.service.search.SearchEngineErrors; import org.openmetadata.service.search.SearchIndexRetryQueue; import org.openmetadata.service.search.SearchIndexUtils; import org.openmetadata.service.search.SearchRetryUtil; @@ -644,12 +644,9 @@ public Response getDocByID(String indexName, String entityId) throws IOException return getResponse(Response.Status.NOT_FOUND, "Document not found."); } + String index = Entity.getSearchRepository().getIndexOrAliasName(indexName); try { - GetResponse response = - client.get( - g -> - g.index(Entity.getSearchRepository().getIndexOrAliasName(indexName)).id(entityId), - Map.class); + GetResponse response = client.get(g -> g.index(index).id(entityId), Map.class); // This path runs no query and has no SubjectContext, so it cannot tell whose restricted // memory a document is: a non-org-wide memory reads as not found rather than leaking. @@ -660,8 +657,7 @@ public Response getDocByID(String indexName, String entityId) throws IOException } } catch (OpenSearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException( - String.format("Failed to find doc with id %s", entityId)); + throw SearchEngineErrors.indexNotFound(index); } else { throw new SearchException(String.format("Search failed due to %s", e.getMessage())); } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchSearchManager.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchSearchManager.java index 34d3064715e7..953260681249 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchSearchManager.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/opensearch/OpenSearchSearchManager.java @@ -56,7 +56,6 @@ import org.openmetadata.schema.type.Include; import org.openmetadata.schema.utils.JsonUtils; import org.openmetadata.sdk.exception.SearchException; -import org.openmetadata.sdk.exception.SearchIndexNotFoundException; import org.openmetadata.service.Entity; import org.openmetadata.service.config.CacheConfiguration; import org.openmetadata.service.jdbi3.ListFilter; @@ -354,7 +353,7 @@ private SearchResultListMapper doListWithOffset( return new SearchResultListMapper(results, totalHits); } catch (OpenSearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException(String.format("Failed to find index %s", index)); + throw SearchEngineErrors.indexNotFound(index); } else { throw buildSearchException(e); } @@ -555,7 +554,7 @@ public SearchResultListMapper listWithDeepPagination( results, totalHits, lastHitSortValues, lastDocumentsInBatch); } catch (OpenSearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException(String.format("Failed to find index %s", index)); + throw SearchEngineErrors.indexNotFound(index); } else { throw buildSearchException(e); } @@ -1216,8 +1215,7 @@ public Response doSearch( } catch (OpenSearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException( - String.format("Failed to find index %s", request.getIndex())); + throw SearchEngineErrors.indexNotFound(request.getIndex()); } else { throw buildSearchException(e); } @@ -1404,8 +1402,7 @@ public SearchResultListMapper searchForExport( return new SearchResultListMapper(results, totalHits, lastHitSortValues); } catch (OpenSearchException e) { if (e.status() == 404) { - throw new SearchIndexNotFoundException( - String.format("Failed to find index %s", request.getIndex())); + throw SearchEngineErrors.indexNotFound(request.getIndex()); } else { throw buildSearchException(e); } diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchEngineErrorsTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchEngineErrorsTest.java index 82ec269b483e..48deba1f302b 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchEngineErrorsTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchEngineErrorsTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.openmetadata.sdk.exception.SearchException; +import org.openmetadata.sdk.exception.SearchIndexNotFoundException; class SearchEngineErrorsTest { @@ -64,6 +65,27 @@ void rendersRootCauses() { assertTrue(thrown.getMessage().contains("other: reason"), thrown.getMessage()); } + @Test + @DisplayName("a missing index the caller made up is the caller's error") + void indexNotFoundForAnUnknownNameIsTheCallersError() { + SearchIndexNotFoundException thrown = + SearchEngineErrors.indexNotFound("cluster_nope", List.of("nope")); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), thrown.getResponse().getStatus()); + assertEquals("Unknown search index [nope]", thrown.getMessage()); + } + + @Test + @DisplayName("a managed index missing from the cluster stays a server fault") + void indexNotFoundForAManagedIndexStaysServerError() { + SearchIndexNotFoundException thrown = + SearchEngineErrors.indexNotFound("cluster_table_search_index", List.of()); + + assertEquals( + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), thrown.getResponse().getStatus()); + assertEquals("Failed to find index cluster_table_search_index", thrown.getMessage()); + } + @Test @DisplayName("no root causes leaves the engine message intact") void rendersMessageWithoutRootCauses() { diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchIndexUtilsTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchIndexUtilsTest.java index e2f879ae0ea2..f029b17c65bd 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchIndexUtilsTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchIndexUtilsTest.java @@ -118,6 +118,61 @@ void getEntityTypesForIndexSupportsEmptyRegistryAndClusterPrefix() { "table_search_index", resolutionMappings(), Map.of(), "")); } + @Test + void knownIndexNamesCoversEntityTypesIndexNamesAndAliases() { + Set known = SearchIndexUtils.knownIndexNames(resolutionMappings()); + + assertTrue(known.containsAll(List.of("table", "tableColumn", "customEntity"))); + assertTrue(known.containsAll(List.of("table_search_index", "custom_search_index"))); + assertTrue(known.containsAll(List.of("all", "dataAsset"))); + assertFalse(known.contains("nope_search_index")); + assertEquals(Set.of(), SearchIndexUtils.knownIndexNames(null)); + } + + @ParameterizedTest + @MethodSource("typedIndexes") + void unknownIndexNamesNamesOnlyWhatOpenMetadataDoesNotManage( + String typed, List expected) { + Set known = SearchIndexUtils.knownIndexNames(resolutionMappings()); + for (String clusterAlias : new String[] {"cluster", null}) { + String resolved = + SearchIndexUtils.getIndexOrAliasName(typed, resolutionMappings(), Map.of(), clusterAlias); + assertEquals( + expected, + SearchIndexUtils.unknownIndexNames(resolved, known, clusterAlias), + typed + " with cluster alias " + clusterAlias); + } + } + + static Stream typedIndexes() { + return Stream.of( + Arguments.of("table", List.of()), + Arguments.of("table_search_index", List.of()), + Arguments.of("all", List.of()), + Arguments.of("dataAsset", List.of()), + Arguments.of("nope_search_index", List.of("nope_search_index")), + Arguments.of("table,nope", List.of("nope")), + Arguments.of("nope,nope", List.of("nope")), + Arguments.of("nope*", List.of()), + Arguments.of("di-data-assets-table", List.of())); + } + + @Test + void unknownIndexNamesStripsTheClusterPrefixTheCallerAlreadyTyped() { + Set known = SearchIndexUtils.knownIndexNames(resolutionMappings()); + assertEquals( + List.of(), + SearchIndexUtils.unknownIndexNames("cluster_table_search_index", known, "cluster")); + assertEquals( + List.of("nope"), SearchIndexUtils.unknownIndexNames("cluster_nope", known, "cluster")); + } + + @Test + void unknownIndexNamesKeepsServerErrorWhenItCannotClassify() { + assertEquals(List.of(), SearchIndexUtils.unknownIndexNames("nope", Set.of(), "cluster")); + assertEquals(List.of(), SearchIndexUtils.unknownIndexNames(null, Set.of("table"), null)); + } + @Test void getIndexOrAliasNamePreservesNullAndUnknownTargetsWithoutRegistry() { assertNull(SearchIndexUtils.getIndexOrAliasName(null, null, null, null)); diff --git a/openmetadata-spec/src/main/java/org/openmetadata/sdk/exception/SearchIndexNotFoundException.java b/openmetadata-spec/src/main/java/org/openmetadata/sdk/exception/SearchIndexNotFoundException.java index a58dddbe5faa..0f2c6dab0d3d 100644 --- a/openmetadata-spec/src/main/java/org/openmetadata/sdk/exception/SearchIndexNotFoundException.java +++ b/openmetadata-spec/src/main/java/org/openmetadata/sdk/exception/SearchIndexNotFoundException.java @@ -15,6 +15,14 @@ private SearchIndexNotFoundException(Response.Status status, String message) { super(status, ERROR_TYPE, message); } + /** + * Reports {@code message} under an explicit status. A search against a name OpenMetadata does not + * manage is the caller's mistake, a 400, not the 500 the single-argument constructor implies. + */ + public static SearchIndexNotFoundException withStatus(Response.Status status, String message) { + return new SearchIndexNotFoundException(status, message); + } + public static SearchIndexNotFoundException byMessage( String name, String errorMessage, Response.Status status) { return new SearchIndexNotFoundException(status, buildMessageByName(name, errorMessage));