From 335d52ddd033a82dcbca44a45ceae9426ba5f657 Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 3 Oct 2025 10:22:03 +0100 Subject: [PATCH 1/6] Remove unnecessary `RestHandler#supportsBulkContent` This method only exists to add some additional `Content-type` validation, but we can do all the validation needed using `RestHandler#mediaTypesValid` instead. --- .../RestMultiSearchTemplateAction.java | 5 +++-- .../elasticsearch/rest/FilterRestHandler.java | 5 ----- .../elasticsearch/rest/RestController.java | 14 ------------- .../org/elasticsearch/rest/RestHandler.java | 10 --------- .../rest/action/document/RestBulkAction.java | 14 +++++++++---- .../action/search/RestMultiSearchAction.java | 5 +++-- .../rest/DeprecationRestHandlerTests.java | 16 -------------- .../rest/RestControllerTests.java | 21 ++++++++++--------- .../xpack/ml/rest/job/RestPostDataAction.java | 5 +++-- .../rest/action/RestMonitoringBulkAction.java | 5 +++-- .../action/RestMonitoringBulkActionTests.java | 11 +++++++++- ...estMonitoringMigrateAlertsActionTests.java | 14 +++++++++++-- 12 files changed, 55 insertions(+), 70 deletions(-) diff --git a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java index 0b497d1122914..6425fb45c8458 100644 --- a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java +++ b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java @@ -16,6 +16,7 @@ import org.elasticsearch.rest.Scope; import org.elasticsearch.rest.ServerlessScope; import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.rest.action.search.RestMultiSearchAction; import org.elasticsearch.rest.action.search.RestSearchAction; @@ -95,8 +96,8 @@ public MultiSearchTemplateRequest parseRequest(RestRequest restRequest, boolean } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } @Override diff --git a/server/src/main/java/org/elasticsearch/rest/FilterRestHandler.java b/server/src/main/java/org/elasticsearch/rest/FilterRestHandler.java index 21a44ac9af5c8..130b0b1dafe82 100644 --- a/server/src/main/java/org/elasticsearch/rest/FilterRestHandler.java +++ b/server/src/main/java/org/elasticsearch/rest/FilterRestHandler.java @@ -43,11 +43,6 @@ public boolean canTripCircuitBreaker() { return delegate.canTripCircuitBreaker(); } - @Override - public boolean supportsBulkContent() { - return delegate.supportsBulkContent(); - } - @Override public boolean mediaTypesValid(RestRequest request) { return delegate.mediaTypesValid(request); diff --git a/server/src/main/java/org/elasticsearch/rest/RestController.java b/server/src/main/java/org/elasticsearch/rest/RestController.java index 66532026fc1ca..6fca7c2d60c6d 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestController.java +++ b/server/src/main/java/org/elasticsearch/rest/RestController.java @@ -421,20 +421,6 @@ private void dispatchRequest( sendContentTypeErrorMessage(request.getAllHeaderValues("Content-Type"), channel); return; } - final XContentType xContentType = request.getXContentType(); - // TODO consider refactoring to handler.supportsContentStream(xContentType). It is only used with JSON and SMILE - if (handler.supportsBulkContent() - && XContentType.JSON != xContentType.canonical() - && XContentType.SMILE != xContentType.canonical()) { - channel.sendResponse( - RestResponse.createSimpleErrorResponse( - channel, - RestStatus.NOT_ACCEPTABLE, - "Content-Type [" + xContentType + "] does not support stream parsing. Use JSON or SMILE instead" - ) - ); - return; - } } RestChannel responseChannel = channel; if (apiProtections.isEnabled()) { diff --git a/server/src/main/java/org/elasticsearch/rest/RestHandler.java b/server/src/main/java/org/elasticsearch/rest/RestHandler.java index 5e08d4900dc75..ed8263e7e83e2 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestHandler.java +++ b/server/src/main/java/org/elasticsearch/rest/RestHandler.java @@ -15,7 +15,6 @@ import org.elasticsearch.core.Nullable; import org.elasticsearch.core.RestApiVersion; import org.elasticsearch.rest.RestRequest.Method; -import org.elasticsearch.xcontent.XContent; import java.util.Collections; import java.util.List; @@ -44,15 +43,6 @@ default boolean supportsContentStream() { return false; } - /** - * Indicates if the RestHandler supports bulk content. A bulk request contains multiple objects - * delineated by {@link XContent#bulkSeparator()}. If a handler returns true this will affect - * the types of content that can be sent to this endpoint. - */ - default boolean supportsBulkContent() { - return false; - } - /** * Returns the concrete RestHandler for this RestHandler. That is, if this is a delegating RestHandler it returns the delegate. * Otherwise it returns itself. diff --git a/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java b/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java index 20a9fb7ed23aa..8f3a25b5c4e15 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java @@ -36,6 +36,7 @@ import org.elasticsearch.rest.action.RestToXContentListener; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.transport.Transports; +import org.elasticsearch.xcontent.XContentType; import java.io.IOException; import java.util.ArrayDeque; @@ -303,12 +304,17 @@ private ArrayList accountParsing(int bytesConsumed) { } @Override - public boolean supportsBulkContent() { - return true; + public Set supportedCapabilities() { + return capabilities; } @Override - public Set supportedCapabilities() { - return capabilities; + public boolean mediaTypesValid(RestRequest request) { + return super.mediaTypesValid(request) && hasValidMediaTypeForBulkRequest(request); + } + + public static boolean hasValidMediaTypeForBulkRequest(RestRequest request) { + final var xContentType = request.getXContentType(); + return xContentType != null && (xContentType.canonical() == XContentType.JSON || xContentType.canonical() == XContentType.SMILE); } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java b/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java index 8546faed767ea..81244dc51cdde 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java @@ -27,6 +27,7 @@ import org.elasticsearch.rest.ServerlessScope; import org.elasticsearch.rest.action.RestCancellableNodeClient; import org.elasticsearch.rest.action.RestRefCountedChunkedToXContentListener; +import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.usage.SearchUsageHolder; import org.elasticsearch.xcontent.XContent; @@ -209,8 +210,8 @@ public static void parseMultiLineRequest( } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } @Override diff --git a/server/src/test/java/org/elasticsearch/rest/DeprecationRestHandlerTests.java b/server/src/test/java/org/elasticsearch/rest/DeprecationRestHandlerTests.java index b534a6be0dc5f..abd0f6bc96e84 100644 --- a/server/src/test/java/org/elasticsearch/rest/DeprecationRestHandlerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/DeprecationRestHandlerTests.java @@ -156,22 +156,6 @@ public void testInvalidHeaderValueEmpty() { expectThrows(IllegalArgumentException.class, () -> DeprecationRestHandler.requireValidHeader(blank)); } - public void testSupportsBulkContentTrue() { - when(handler.supportsBulkContent()).thenReturn(true); - assertTrue( - new DeprecationRestHandler(handler, METHOD, PATH, Level.WARN, deprecationMessage, deprecationLogger, false) - .supportsBulkContent() - ); - } - - public void testSupportsBulkContentFalse() { - when(handler.supportsBulkContent()).thenReturn(false); - assertFalse( - new DeprecationRestHandler(handler, METHOD, PATH, Level.WARN, deprecationMessage, deprecationLogger, false) - .supportsBulkContent() - ); - } - public void testDeprecationLevel() { DeprecationRestHandler handler = new DeprecationRestHandler( this.handler, diff --git a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java index 28fe8ad5b6fa4..16452a9e56e19 100644 --- a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java @@ -40,6 +40,7 @@ import org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService; import org.elasticsearch.rest.RestHandler.Route; import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.telemetry.TelemetryProvider; import org.elasticsearch.telemetry.metric.LongCounter; import org.elasticsearch.telemetry.metric.MeterRegistry; @@ -640,8 +641,8 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } }); @@ -679,8 +680,8 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } }); @@ -704,8 +705,8 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } }); @@ -730,8 +731,8 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } }); assertFalse(channel.getSendResponseCalled()); @@ -755,8 +756,8 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } }); assertFalse(channel.getSendResponseCalled()); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/job/RestPostDataAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/job/RestPostDataAction.java index 443e15a6018e3..464f8d65202ce 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/job/RestPostDataAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/job/RestPostDataAction.java @@ -12,6 +12,7 @@ import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.RestToXContentListener; +import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.xpack.core.ml.action.PostDataAction; import org.elasticsearch.xpack.core.ml.job.config.Job; @@ -54,7 +55,7 @@ protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } } diff --git a/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkAction.java b/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkAction.java index 762cbffacb082..8e1658dea9169 100644 --- a/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkAction.java +++ b/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkAction.java @@ -15,6 +15,7 @@ import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.action.RestBuilderListener; +import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xpack.core.monitoring.MonitoredSystem; import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkRequestBuilder; @@ -100,8 +101,8 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client } @Override - public boolean supportsBulkContent() { - return true; + public boolean mediaTypesValid(RestRequest request) { + return super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); } /** diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkActionTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkActionTests.java index bf3ea4492a52c..c1c89866a9f1e 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkActionTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkActionTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.RestBuilderListener; +import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.xcontent.NamedXContentRegistry; @@ -28,6 +29,7 @@ import org.elasticsearch.xpack.core.monitoring.exporter.MonitoringTemplateUtils; import java.util.HashMap; +import java.util.List; import java.util.Map; import static org.elasticsearch.xpack.core.monitoring.exporter.MonitoringTemplateUtils.TEMPLATE_VERSION; @@ -47,7 +49,14 @@ public void testGetName() { public void testSupportsBulkContent() { // if you change this, it's a very breaking change for Monitoring - assertThat(action.supportsBulkContent(), is(true)); + final var route = action.routes().get(0); + for (var xContentType : XContentType.values()) { + final var request = new FakeRestRequest.Builder(xContentRegistry()).withMethod(route.getMethod()) + .withPath(route.getPath()) + .withHeaders(Map.of("Content-Type", List.of(xContentType.mediaType()))) + .build(); + assertEquals(xContentType.toString(), RestBulkAction.hasValidMediaTypeForBulkRequest(request), action.mediaTypesValid(request)); + } } public void testMissingSystemId() { diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringMigrateAlertsActionTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringMigrateAlertsActionTests.java index 776e7937fe69d..45b0e7d518288 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringMigrateAlertsActionTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringMigrateAlertsActionTests.java @@ -11,6 +11,8 @@ import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.rest.FakeRestRequest; +import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xpack.core.monitoring.action.MonitoringMigrateAlertsResponse; import org.elasticsearch.xpack.core.monitoring.action.MonitoringMigrateAlertsResponse.ExporterMigrationResult; @@ -20,6 +22,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; @@ -34,8 +37,15 @@ public void testGetName() { assertThat(action.getName(), is("monitoring_migrate_alerts")); } - public void testSupportsContentStream() { - assertThat(action.supportsBulkContent(), is(false)); + public void testSupportsAllContentTypes() { + final var route = action.routes().get(0); + for (var xContentType : XContentType.values()) { + final var request = new FakeRestRequest.Builder(xContentRegistry()).withMethod(route.getMethod()) + .withPath(route.getPath()) + .withHeaders(Map.of("Content-Type", List.of(xContentType.mediaType()))) + .build(); + assertTrue(xContentType.toString(), action.mediaTypesValid(request)); + } } public void testRestActionCompletion() throws Exception { From 83c3d82a47527a21ef5c2aad80d7fd021d2727fd Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 3 Oct 2025 11:38:33 +0100 Subject: [PATCH 2/6] Reinstate comment --- .../elasticsearch/rest/action/document/RestBulkAction.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java b/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java index 8f3a25b5c4e15..e4ac0d4570e33 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java @@ -36,6 +36,7 @@ import org.elasticsearch.rest.action.RestToXContentListener; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.transport.Transports; +import org.elasticsearch.xcontent.XContent; import org.elasticsearch.xcontent.XContentType; import java.io.IOException; @@ -313,6 +314,11 @@ public boolean mediaTypesValid(RestRequest request) { return super.mediaTypesValid(request) && hasValidMediaTypeForBulkRequest(request); } + /** + * Indicates if the request has a {@code Content-Type} compatible with bulk content. A bulk request contains multiple objects + * each terminated with {@link XContent#bulkSeparator()}. If a handler returns true this will affect the types of content that can be + * sent to this endpoint. + */ public static boolean hasValidMediaTypeForBulkRequest(RestRequest request) { final var xContentType = request.getXContentType(); return xContentType != null && (xContentType.canonical() == XContentType.JSON || xContentType.canonical() == XContentType.SMILE); From b208ac4ef48337d4f85b59ffac4ff37f3f58a130 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 3 Oct 2025 10:46:57 +0000 Subject: [PATCH 3/6] [CI] Auto commit changes from spotless --- .../java/org/elasticsearch/rest/DeprecationRestHandlerTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/test/java/org/elasticsearch/rest/DeprecationRestHandlerTests.java b/server/src/test/java/org/elasticsearch/rest/DeprecationRestHandlerTests.java index abd0f6bc96e84..b993fae4a857e 100644 --- a/server/src/test/java/org/elasticsearch/rest/DeprecationRestHandlerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/DeprecationRestHandlerTests.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; /** * Tests {@link DeprecationRestHandler}. From d282db9c580e2b8edaa63567eacdeefda2d09b1f Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 3 Oct 2025 19:54:23 +0100 Subject: [PATCH 4/6] Move predicate to `XContentType` --- .../xcontent/provider/cbor/CborXContentImpl.java | 5 +++++ .../xcontent/provider/json/JsonXContentImpl.java | 5 +++++ .../xcontent/provider/smile/SmileXContentImpl.java | 5 +++++ .../xcontent/provider/yaml/YamlXContentImpl.java | 5 +++++ .../java/org/elasticsearch/xcontent/XContent.java | 11 +++++++++++ .../org/elasticsearch/xcontent/XContentType.java | 9 +++++++++ .../mustache/RestMultiSearchTemplateAction.java | 4 ++-- .../rest/action/document/RestBulkAction.java | 13 +------------ .../rest/action/search/RestMultiSearchAction.java | 3 +-- .../org/elasticsearch/rest/RestControllerTests.java | 11 +++++------ .../xpack/ml/rest/job/RestPostDataAction.java | 4 ++-- .../rest/action/RestMonitoringBulkAction.java | 4 ++-- .../rest/action/RestMonitoringBulkActionTests.java | 7 +++++-- 13 files changed, 58 insertions(+), 28 deletions(-) diff --git a/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/cbor/CborXContentImpl.java b/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/cbor/CborXContentImpl.java index 063db426d87c0..bd78062a861fe 100644 --- a/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/cbor/CborXContentImpl.java +++ b/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/cbor/CborXContentImpl.java @@ -63,6 +63,11 @@ public XContentType type() { return XContentType.CBOR; } + @Override + public boolean hasBulkSeparator() { + return false; + } + @Override public byte bulkSeparator() { throw new XContentParseException("cbor does not support bulk parsing..."); diff --git a/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/JsonXContentImpl.java b/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/JsonXContentImpl.java index d109fd28dd839..d0bcae054f1f5 100644 --- a/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/JsonXContentImpl.java +++ b/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/json/JsonXContentImpl.java @@ -68,6 +68,11 @@ public XContentType type() { return XContentType.JSON; } + @Override + public boolean hasBulkSeparator() { + return true; + } + @Override public byte bulkSeparator() { return '\n'; diff --git a/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/smile/SmileXContentImpl.java b/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/smile/SmileXContentImpl.java index a2139b519d0a0..a54ee8cff55b1 100644 --- a/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/smile/SmileXContentImpl.java +++ b/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/smile/SmileXContentImpl.java @@ -70,6 +70,11 @@ public byte bulkSeparator() { return (byte) 0xFF; } + @Override + public boolean hasBulkSeparator() { + return true; + } + @Override public boolean detectContent(byte[] bytes, int offset, int length) { return length > 2 diff --git a/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/yaml/YamlXContentImpl.java b/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/yaml/YamlXContentImpl.java index 69d9acc50cae9..ec2a7535d2df6 100644 --- a/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/yaml/YamlXContentImpl.java +++ b/libs/x-content/impl/src/main/java/org/elasticsearch/xcontent/provider/yaml/YamlXContentImpl.java @@ -61,6 +61,11 @@ public XContentType type() { return XContentType.YAML; } + @Override + public boolean hasBulkSeparator() { + return false; + } + @Override public byte bulkSeparator() { throw new UnsupportedOperationException("yaml does not support bulk parsing..."); diff --git a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java index 3325287fd6abf..397a848623eaa 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java +++ b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java @@ -25,6 +25,17 @@ public interface XContent { */ XContentType type(); + /** + * @return {@code true} if this {@link XContent} can be sent in bulk, delimited by the byte returned by {@link #bulkSeparator()}, or + * {@code false} if this {@link XContent} does not support a delimited bulk format (in which case {@link #bulkSeparator()} throws an + * exception. + */ + boolean hasBulkSeparator(); + + /** + * @return a {@link byte} that separates items in a bulk request that uses this {@link XContent}. + * @throws RuntimeException if this {@link XContent} does not support a delimited bulk format. See {@link #hasBulkSeparator()}. + */ byte bulkSeparator(); @Deprecated diff --git a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java index 4a6547b307a61..eb819d8715da2 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java @@ -9,6 +9,7 @@ package org.elasticsearch.xcontent; +import org.elasticsearch.core.Nullable; import org.elasticsearch.xcontent.cbor.CborXContent; import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xcontent.smile.SmileXContent; @@ -287,4 +288,12 @@ public XContentType canonical() { public static XContentType ofOrdinal(int ordinal) { return values[ordinal]; } + + /** + * Indicates if the {@link XContentType} (from the {@code Content-Type} header of a REST request) is compatible with delimited bulk + * content. A bulk request contains multiple objects each terminated with {@link XContent#bulkSeparator()}. + */ + public static boolean supportsDelimitedBulkRequests(@Nullable XContentType xContentType) { + return xContentType != null && xContentType.xContent.hasBulkSeparator(); + } } diff --git a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java index 6425fb45c8458..5a5b1df64907d 100644 --- a/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java +++ b/modules/lang-mustache/src/main/java/org/elasticsearch/script/mustache/RestMultiSearchTemplateAction.java @@ -16,9 +16,9 @@ import org.elasticsearch.rest.Scope; import org.elasticsearch.rest.ServerlessScope; import org.elasticsearch.rest.action.RestToXContentListener; -import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.rest.action.search.RestMultiSearchAction; import org.elasticsearch.rest.action.search.RestSearchAction; +import org.elasticsearch.xcontent.XContentType; import java.io.IOException; import java.util.List; @@ -97,7 +97,7 @@ public MultiSearchTemplateRequest parseRequest(RestRequest restRequest, boolean @Override public boolean mediaTypesValid(RestRequest request) { - return super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } @Override diff --git a/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java b/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java index e4ac0d4570e33..e381b1c207072 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/document/RestBulkAction.java @@ -36,7 +36,6 @@ import org.elasticsearch.rest.action.RestToXContentListener; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.elasticsearch.transport.Transports; -import org.elasticsearch.xcontent.XContent; import org.elasticsearch.xcontent.XContentType; import java.io.IOException; @@ -311,16 +310,6 @@ public Set supportedCapabilities() { @Override public boolean mediaTypesValid(RestRequest request) { - return super.mediaTypesValid(request) && hasValidMediaTypeForBulkRequest(request); - } - - /** - * Indicates if the request has a {@code Content-Type} compatible with bulk content. A bulk request contains multiple objects - * each terminated with {@link XContent#bulkSeparator()}. If a handler returns true this will affect the types of content that can be - * sent to this endpoint. - */ - public static boolean hasValidMediaTypeForBulkRequest(RestRequest request) { - final var xContentType = request.getXContentType(); - return xContentType != null && (xContentType.canonical() == XContentType.JSON || xContentType.canonical() == XContentType.SMILE); + return super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java b/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java index 81244dc51cdde..9edaff7563db5 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/search/RestMultiSearchAction.java @@ -27,7 +27,6 @@ import org.elasticsearch.rest.ServerlessScope; import org.elasticsearch.rest.action.RestCancellableNodeClient; import org.elasticsearch.rest.action.RestRefCountedChunkedToXContentListener; -import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.usage.SearchUsageHolder; import org.elasticsearch.xcontent.XContent; @@ -211,7 +210,7 @@ public static void parseMultiLineRequest( @Override public boolean mediaTypesValid(RestRequest request) { - return super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } @Override diff --git a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java index 16452a9e56e19..027f627604cf2 100644 --- a/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java +++ b/server/src/test/java/org/elasticsearch/rest/RestControllerTests.java @@ -40,7 +40,6 @@ import org.elasticsearch.indices.breaker.HierarchyCircuitBreakerService; import org.elasticsearch.rest.RestHandler.Route; import org.elasticsearch.rest.action.RestToXContentListener; -import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.telemetry.TelemetryProvider; import org.elasticsearch.telemetry.metric.LongCounter; import org.elasticsearch.telemetry.metric.MeterRegistry; @@ -642,7 +641,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c @Override public boolean mediaTypesValid(RestRequest request) { - return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return RestHandler.super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } }); @@ -681,7 +680,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c @Override public boolean mediaTypesValid(RestRequest request) { - return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return RestHandler.super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } }); @@ -706,7 +705,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c @Override public boolean mediaTypesValid(RestRequest request) { - return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return RestHandler.super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } }); @@ -732,7 +731,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c @Override public boolean mediaTypesValid(RestRequest request) { - return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return RestHandler.super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } }); assertFalse(channel.getSendResponseCalled()); @@ -757,7 +756,7 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c @Override public boolean mediaTypesValid(RestRequest request) { - return RestHandler.super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return RestHandler.super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } }); assertFalse(channel.getSendResponseCalled()); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/job/RestPostDataAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/job/RestPostDataAction.java index 464f8d65202ce..f11ecf65eeccf 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/job/RestPostDataAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/rest/job/RestPostDataAction.java @@ -12,7 +12,7 @@ import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.RestToXContentListener; -import org.elasticsearch.rest.action.document.RestBulkAction; +import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xpack.core.ml.action.PostDataAction; import org.elasticsearch.xpack.core.ml.job.config.Job; @@ -56,6 +56,6 @@ protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient @Override public boolean mediaTypesValid(RestRequest request) { - return super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } } diff --git a/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkAction.java b/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkAction.java index 8e1658dea9169..57fbfbe29b4c8 100644 --- a/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkAction.java +++ b/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkAction.java @@ -15,8 +15,8 @@ import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.action.RestBuilderListener; -import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xpack.core.monitoring.MonitoredSystem; import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkRequestBuilder; import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkResponse; @@ -102,7 +102,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public boolean mediaTypesValid(RestRequest request) { - return super.mediaTypesValid(request) && RestBulkAction.hasValidMediaTypeForBulkRequest(request); + return super.mediaTypesValid(request) && XContentType.supportsDelimitedBulkRequests(request.getXContentType()); } /** diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkActionTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkActionTests.java index c1c89866a9f1e..b32fa1103ab38 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkActionTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/rest/action/RestMonitoringBulkActionTests.java @@ -16,7 +16,6 @@ import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.RestBuilderListener; -import org.elasticsearch.rest.action.document.RestBulkAction; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.xcontent.NamedXContentRegistry; @@ -55,7 +54,11 @@ public void testSupportsBulkContent() { .withPath(route.getPath()) .withHeaders(Map.of("Content-Type", List.of(xContentType.mediaType()))) .build(); - assertEquals(xContentType.toString(), RestBulkAction.hasValidMediaTypeForBulkRequest(request), action.mediaTypesValid(request)); + assertEquals( + xContentType.toString(), + XContentType.supportsDelimitedBulkRequests(request.getXContentType()), + action.mediaTypesValid(request) + ); } } From b69b129b02bfa7d59a2326121b20f7693fd29876 Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 3 Oct 2025 19:54:23 +0100 Subject: [PATCH 5/6] Comments --- .../src/main/java/org/elasticsearch/xcontent/XContent.java | 3 +++ .../src/main/java/org/elasticsearch/xcontent/XContentType.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java index 397a848623eaa..56d6bc9b100b2 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java +++ b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java @@ -29,6 +29,9 @@ public interface XContent { * @return {@code true} if this {@link XContent} can be sent in bulk, delimited by the byte returned by {@link #bulkSeparator()}, or * {@code false} if this {@link XContent} does not support a delimited bulk format (in which case {@link #bulkSeparator()} throws an * exception. + *

+ * In practice, this is {@code true} for content with canonical type {@link XContentType#JSON} or {@link XContentType#SMILE} and + * {@link false} for content with canonical type {@link XContentType#CBOR} or {@link XContentType#YAML}. */ boolean hasBulkSeparator(); diff --git a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java index eb819d8715da2..acc515d9dd828 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java @@ -292,6 +292,9 @@ public static XContentType ofOrdinal(int ordinal) { /** * Indicates if the {@link XContentType} (from the {@code Content-Type} header of a REST request) is compatible with delimited bulk * content. A bulk request contains multiple objects each terminated with {@link XContent#bulkSeparator()}. + *

+ * In practice, this returns {@code true} if the argument has canonical type {@link XContentType#JSON} or {@link XContentType#SMILE} + * and {@link false} if the argument is {@code null} or has canonical type {@link XContentType#CBOR} or {@link XContentType#YAML}. */ public static boolean supportsDelimitedBulkRequests(@Nullable XContentType xContentType) { return xContentType != null && xContentType.xContent.hasBulkSeparator(); From eaea828df9b920573ad4f98534a108266a3a7794 Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 3 Oct 2025 20:44:25 +0100 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/elasticsearch/xcontent/XContent.java | 2 +- .../src/main/java/org/elasticsearch/xcontent/XContentType.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java index 56d6bc9b100b2..8d45cf9e7f01c 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java +++ b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContent.java @@ -31,7 +31,7 @@ public interface XContent { * exception. *

* In practice, this is {@code true} for content with canonical type {@link XContentType#JSON} or {@link XContentType#SMILE} and - * {@link false} for content with canonical type {@link XContentType#CBOR} or {@link XContentType#YAML}. + * {@code false} for content with canonical type {@link XContentType#CBOR} or {@link XContentType#YAML}. */ boolean hasBulkSeparator(); diff --git a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java index acc515d9dd828..f31b963b8b127 100644 --- a/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java +++ b/libs/x-content/src/main/java/org/elasticsearch/xcontent/XContentType.java @@ -294,7 +294,7 @@ public static XContentType ofOrdinal(int ordinal) { * content. A bulk request contains multiple objects each terminated with {@link XContent#bulkSeparator()}. *

* In practice, this returns {@code true} if the argument has canonical type {@link XContentType#JSON} or {@link XContentType#SMILE} - * and {@link false} if the argument is {@code null} or has canonical type {@link XContentType#CBOR} or {@link XContentType#YAML}. + * and {@code false} if the argument is {@code null} or has canonical type {@link XContentType#CBOR} or {@link XContentType#YAML}. */ public static boolean supportsDelimitedBulkRequests(@Nullable XContentType xContentType) { return xContentType != null && xContentType.xContent.hasBulkSeparator();