Quellcode durchsuchen

Make some Mapping Constants more Efficient (#80337)

Turns some of these constants into more directly usable compressed x-content.
Also, moves some code that did redundant round trips to `String` to parse x-content
directly. Also, this sets up a couple of follow-ups to get rid of most `String` usage that would
speed up mapper parsing.
Armin Braun vor 4 Jahren
Ursprung
Commit
e222ab056b

+ 5 - 2
server/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutIndexTemplateAction.java

@@ -21,6 +21,7 @@ import org.elasticsearch.cluster.metadata.IndexMetadata;
 import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
 import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService;
 import org.elasticsearch.cluster.service.ClusterService;
+import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.IndexScopedSettings;
 import org.elasticsearch.common.settings.Settings;
@@ -28,6 +29,8 @@ import org.elasticsearch.tasks.Task;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
+import java.io.IOException;
+
 /**
  * Put index template action.
  */
@@ -73,7 +76,7 @@ public class TransportPutIndexTemplateAction extends AcknowledgedTransportMaster
         final PutIndexTemplateRequest request,
         final ClusterState state,
         final ActionListener<AcknowledgedResponse> listener
-    ) {
+    ) throws IOException {
         String cause = request.cause();
         if (cause.length() == 0) {
             cause = "api";
@@ -85,7 +88,7 @@ public class TransportPutIndexTemplateAction extends AcknowledgedTransportMaster
             new MetadataIndexTemplateService.PutRequest(cause, request.name()).patterns(request.patterns())
                 .order(request.order())
                 .settings(templateSettingsBuilder.build())
-                .mappings(request.mappings())
+                .mappings(request.mappings() == null ? null : new CompressedXContent(request.mappings()))
                 .aliases(request.aliases())
                 .create(request.create())
                 .masterTimeout(request.masterNodeTimeout())

+ 1 - 2
server/src/main/java/org/elasticsearch/action/bulk/TransportShardBulkAction.java

@@ -59,7 +59,6 @@ import org.elasticsearch.node.NodeClosedException;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportRequestOptions;
 import org.elasticsearch.transport.TransportService;
-import org.elasticsearch.xcontent.ToXContent;
 import org.elasticsearch.xcontent.XContentType;
 
 import java.io.IOException;
@@ -345,7 +344,7 @@ public class TransportShardBulkAction extends TransportWriteAction<BulkShardRequ
                 primary.mapperService()
                     .merge(
                         MapperService.SINGLE_MAPPING_NAME,
-                        new CompressedXContent(result.getRequiredMappingUpdate(), XContentType.JSON, ToXContent.EMPTY_PARAMS),
+                        new CompressedXContent(result.getRequiredMappingUpdate()),
                         MapperService.MergeReason.MAPPING_UPDATE_PREFLIGHT
                     );
             } catch (Exception e) {

+ 15 - 4
server/src/main/java/org/elasticsearch/cluster/metadata/ComposableIndexTemplate.java

@@ -12,6 +12,7 @@ import org.elasticsearch.Version;
 import org.elasticsearch.cluster.AbstractDiffable;
 import org.elasticsearch.cluster.Diff;
 import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.Writeable;
@@ -329,11 +330,21 @@ public class ComposableIndexTemplate extends AbstractDiffable<ComposableIndexTem
         }
 
         /**
-         * @return a mapping snippet for a backing index with `_data_stream_timestamp` meta field mapper properly configured.
+         * A mapping snippet for a backing index with `_data_stream_timestamp` meta field mapper properly configured.
          */
-        public Map<String, Object> getDataStreamMappingSnippet() {
-            // _data_stream_timestamp meta fields default to @timestamp:
-            return Map.of(MapperService.SINGLE_MAPPING_NAME, Map.of(DataStreamTimestampFieldMapper.NAME, Map.of("enabled", true)));
+        public static CompressedXContent DATA_STREAM_MAPPING_SNIPPET;
+
+        static {
+            try {
+                DATA_STREAM_MAPPING_SNIPPET = new CompressedXContent(
+                    (builder, params) -> builder.field(
+                        MapperService.SINGLE_MAPPING_NAME,
+                        Map.of(DataStreamTimestampFieldMapper.NAME, Map.of("enabled", true))
+                    )
+                );
+            } catch (IOException e) {
+                throw new AssertionError("no actual IO happens here", e);
+            }
         }
 
         public boolean isHidden() {

+ 1 - 7
server/src/main/java/org/elasticsearch/cluster/metadata/MappingMetadata.java

@@ -19,8 +19,6 @@ import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.index.mapper.DocumentMapper;
 import org.elasticsearch.index.mapper.MapperService;
-import org.elasticsearch.xcontent.ToXContent;
-import org.elasticsearch.xcontent.XContentType;
 
 import java.io.IOException;
 import java.io.UncheckedIOException;
@@ -64,11 +62,7 @@ public class MappingMetadata extends AbstractDiffable<MappingMetadata> {
     public MappingMetadata(String type, Map<String, Object> mapping) {
         this.type = type;
         try {
-            this.source = new CompressedXContent(
-                (builder, params) -> builder.mapContents(mapping),
-                XContentType.JSON,
-                ToXContent.EMPTY_PARAMS
-            );
+            this.source = new CompressedXContent((builder, params) -> builder.mapContents(mapping));
         } catch (IOException e) {
             throw new UncheckedIOException(e);  // XContent exception, should never happen
         }

+ 4 - 10
server/src/main/java/org/elasticsearch/cluster/metadata/MetadataCreateIndexService.java

@@ -704,8 +704,7 @@ public class MetadataCreateIndexService {
         List<CompressedXContent> templateMappings = MetadataIndexTemplateService.collectMappings(
             composableIndexTemplate,
             componentTemplates,
-            indexName,
-            xContentRegistry
+            indexName
         );
         return collectV2Mappings("{}", templateMappings, xContentRegistry);
     }
@@ -717,12 +716,7 @@ public class MetadataCreateIndexService {
         final NamedXContentRegistry xContentRegistry,
         final String indexName
     ) throws Exception {
-        List<CompressedXContent> templateMappings = MetadataIndexTemplateService.collectMappings(
-            currentState,
-            templateName,
-            indexName,
-            xContentRegistry
-        );
+        List<CompressedXContent> templateMappings = MetadataIndexTemplateService.collectMappings(currentState, templateName, indexName);
         return collectV2Mappings(requestMappings, templateMappings, xContentRegistry);
     }
 
@@ -734,7 +728,7 @@ public class MetadataCreateIndexService {
         List<Map<String, Object>> result = new ArrayList<>();
 
         for (CompressedXContent templateMapping : templateMappings) {
-            Map<String, Object> parsedTemplateMapping = MapperService.parseMapping(xContentRegistry, templateMapping.string());
+            Map<String, Object> parsedTemplateMapping = MapperService.parseMapping(xContentRegistry, templateMapping);
             result.add(parsedTemplateMapping);
         }
 
@@ -814,7 +808,7 @@ public class MetadataCreateIndexService {
         // apply templates, merging the mappings into the request mapping if exists
         for (CompressedXContent mapping : templateMappings) {
             if (mapping != null) {
-                Map<String, Object> templateMapping = MapperService.parseMapping(xContentRegistry, mapping.string());
+                Map<String, Object> templateMapping = MapperService.parseMapping(xContentRegistry, mapping);
                 if (templateMapping.isEmpty()) {
                     // Someone provided an empty '{}' for mappings, which is okay, but to avoid
                     // tripping the below assertion, we can safely ignore it

+ 59 - 81
server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java

@@ -28,7 +28,6 @@ import org.elasticsearch.common.Priority;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.UUIDs;
 import org.elasticsearch.common.ValidationException;
-import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.logging.DeprecationLogger;
@@ -41,19 +40,17 @@ import org.elasticsearch.core.Nullable;
 import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.index.Index;
 import org.elasticsearch.index.IndexService;
+import org.elasticsearch.index.mapper.DateFieldMapper;
 import org.elasticsearch.index.mapper.MapperParsingException;
 import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.mapper.MapperService.MergeReason;
+import org.elasticsearch.index.mapper.RoutingFieldMapper;
 import org.elasticsearch.indices.IndexTemplateMissingException;
 import org.elasticsearch.indices.IndicesService;
 import org.elasticsearch.indices.InvalidIndexTemplateException;
 import org.elasticsearch.xcontent.NamedXContentRegistry;
-import org.elasticsearch.xcontent.XContentBuilder;
-import org.elasticsearch.xcontent.XContentFactory;
-import org.elasticsearch.xcontent.XContentType;
 
 import java.io.IOException;
-import java.io.UncheckedIOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -84,23 +81,35 @@ import static org.elasticsearch.indices.cluster.IndicesClusterStateService.Alloc
 public class MetadataIndexTemplateService {
 
     public static final String DEFAULT_TIMESTAMP_FIELD = "@timestamp";
-    public static final String DEFAULT_TIMESTAMP_MAPPING = "{\n"
-        + "      \"properties\": {\n"
-        + "        \"@timestamp\": {\n"
-        + "          \"type\": \"date\"\n"
-        + "        }\n"
-        + "      }\n"
-        + "    }";
-    public static final String DEFAULT_TIMESTAMP_MAPPING_WITH_ROUTING = "{\n"
-        + "\"_routing\" : {\n"
-        + "        \"required\" : true\n"
-        + "      },"
-        + "      \"properties\": {\n"
-        + "        \"@timestamp\": {\n"
-        + "          \"type\": \"date\"\n"
-        + "        }\n"
-        + "      }\n"
-        + "    }";
+    private static final CompressedXContent DEFAULT_TIMESTAMP_MAPPING;
+
+    private static final CompressedXContent DEFAULT_TIMESTAMP_MAPPING_WITH_ROUTING;
+
+    static {
+        final Map<String, Map<String, String>> defaultTimestampField = Map.of(
+            DEFAULT_TIMESTAMP_FIELD,
+            Map.of("type", DateFieldMapper.CONTENT_TYPE)
+        );
+        try {
+            DEFAULT_TIMESTAMP_MAPPING = new CompressedXContent(
+                (builder, params) -> builder.startObject(MapperService.SINGLE_MAPPING_NAME)
+                    .field("properties", defaultTimestampField)
+                    .endObject()
+            );
+            DEFAULT_TIMESTAMP_MAPPING_WITH_ROUTING = new CompressedXContent(
+                (builder, params) -> builder.startObject(MapperService.SINGLE_MAPPING_NAME)
+                    .startObject(RoutingFieldMapper.NAME)
+                    .field("required", true)
+                    .endObject()
+                    .field("properties")
+                    .map(defaultTimestampField)
+                    .endObject()
+            );
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
+    }
+
     private static final Logger logger = LogManager.getLogger(MetadataIndexTemplateService.class);
     private final ClusterService clusterService;
     private final AliasValidator aliasValidator;
@@ -216,7 +225,7 @@ public class MetadataIndexTemplateService {
         }
 
         CompressedXContent mappings = template.template().mappings();
-        String stringMappings = wrapMappingsIfNecessary(mappings == null ? null : mappings.string(), xContentRegistry);
+        CompressedXContent wrappedMappings = wrapMappingsIfNecessary(mappings, xContentRegistry);
 
         // We may need to normalize index settings, so do that also
         Settings finalSettings = template.template().settings();
@@ -261,18 +270,14 @@ public class MetadataIndexTemplateService {
             }
         }
 
-        final Template finalTemplate = new Template(
-            finalSettings,
-            stringMappings == null ? null : new CompressedXContent(stringMappings),
-            template.template().aliases()
-        );
+        final Template finalTemplate = new Template(finalSettings, wrappedMappings, template.template().aliases());
         final ComponentTemplate finalComponentTemplate = new ComponentTemplate(finalTemplate, template.version(), template.metadata());
 
         if (finalComponentTemplate.equals(existing)) {
             return currentState;
         }
 
-        validateTemplate(finalSettings, stringMappings, indicesService, xContentRegistry);
+        validateTemplate(finalSettings, wrappedMappings, indicesService, xContentRegistry);
         validate(name, finalComponentTemplate);
 
         // Validate all composable index templates that use this component template
@@ -319,30 +324,29 @@ public class MetadataIndexTemplateService {
     }
 
     @Nullable
-    private static String wrapMappingsIfNecessary(@Nullable String mappings, NamedXContentRegistry xContentRegistry) throws Exception {
+    private static CompressedXContent wrapMappingsIfNecessary(@Nullable CompressedXContent mappings, NamedXContentRegistry xContentRegistry)
+        throws IOException {
         // Mappings in templates don't have to include _doc, so update
         // the mappings to include this single type if necessary
 
-        String stringMappings = mappings;
-        if (stringMappings != null) {
-            Map<String, Object> parsedMappings = MapperService.parseMapping(xContentRegistry, stringMappings);
+        CompressedXContent wrapped = mappings;
+        if (wrapped != null) {
+            Map<String, Object> parsedMappings = MapperService.parseMapping(xContentRegistry, mappings);
             if (parsedMappings.size() > 0) {
                 if (parsedMappings.size() == 1) {
                     final String keyName = parsedMappings.keySet().iterator().next();
                     // Check if it's already wrapped in `_doc`, only rewrap if needed
                     if (MapperService.SINGLE_MAPPING_NAME.equals(keyName) == false) {
-                        stringMappings = Strings.toString(
-                            XContentFactory.jsonBuilder().startObject().field(MapperService.SINGLE_MAPPING_NAME, parsedMappings).endObject()
+                        wrapped = new CompressedXContent(
+                            (builder, params) -> builder.field(MapperService.SINGLE_MAPPING_NAME, parsedMappings)
                         );
                     }
                 } else {
-                    stringMappings = Strings.toString(
-                        XContentFactory.jsonBuilder().startObject().field(MapperService.SINGLE_MAPPING_NAME, parsedMappings).endObject()
-                    );
+                    wrapped = new CompressedXContent((builder, params) -> builder.field(MapperService.SINGLE_MAPPING_NAME, parsedMappings));
                 }
             }
         }
-        return stringMappings;
+        return wrapped;
     }
 
     /**
@@ -430,7 +434,7 @@ public class MetadataIndexTemplateService {
     static void validateNotInUse(Metadata metadata, String... templateNameOrWildcard) {
         final Predicate<String> predicate;
         if (templateNameOrWildcard.length > 1) {
-            predicate = name -> Arrays.stream(templateNameOrWildcard).anyMatch(s -> Objects.equals(s, name));
+            predicate = name -> Arrays.asList(templateNameOrWildcard).contains(name);
         } else {
             predicate = name -> Regex.simpleMatch(templateNameOrWildcard[0], name);
         }
@@ -580,13 +584,9 @@ public class MetadataIndexTemplateService {
             // If an inner template was specified, its mappings may need to be
             // adjusted (to add _doc) and it should be validated
             CompressedXContent mappings = innerTemplate.mappings();
-            String stringMappings = wrapMappingsIfNecessary(mappings == null ? null : mappings.string(), xContentRegistry);
+            CompressedXContent wrappedMappings = wrapMappingsIfNecessary(mappings, xContentRegistry);
 
-            final Template finalTemplate = new Template(
-                finalSettings,
-                stringMappings == null ? null : new CompressedXContent(stringMappings),
-                innerTemplate.aliases()
-            );
+            final Template finalTemplate = new Template(finalSettings, wrappedMappings, innerTemplate.aliases());
             finalIndexTemplate = new ComposableIndexTemplate(
                 template.indexPatterns(),
                 finalTemplate,
@@ -1136,12 +1136,8 @@ public class MetadataIndexTemplateService {
     /**
      * Collect the given v2 template into an ordered list of mappings.
      */
-    public static List<CompressedXContent> collectMappings(
-        final ClusterState state,
-        final String templateName,
-        final String indexName,
-        final NamedXContentRegistry xContentRegistry
-    ) throws Exception {
+    public static List<CompressedXContent> collectMappings(final ClusterState state, final String templateName, final String indexName)
+        throws Exception {
         final ComposableIndexTemplate template = state.metadata().templatesV2().get(templateName);
         assert template != null
             : "attempted to resolve mappings for a template [" + templateName + "] that did not exist in the cluster state";
@@ -1150,7 +1146,7 @@ public class MetadataIndexTemplateService {
         }
 
         final Map<String, ComponentTemplate> componentTemplates = state.metadata().componentTemplates();
-        return collectMappings(template, componentTemplates, indexName, xContentRegistry);
+        return collectMappings(template, componentTemplates, indexName);
     }
 
     /**
@@ -1159,8 +1155,7 @@ public class MetadataIndexTemplateService {
     public static List<CompressedXContent> collectMappings(
         final ComposableIndexTemplate template,
         final Map<String, ComponentTemplate> componentTemplates,
-        final String indexName,
-        final NamedXContentRegistry xContentRegistry
+        final String indexName
     ) throws Exception {
         Objects.requireNonNull(template, "Composable index template must be provided");
         List<CompressedXContent> mappings = template.composedOf()
@@ -1177,9 +1172,9 @@ public class MetadataIndexTemplateService {
             // add a default mapping for the `@timestamp` field, at the lowest precedence, to make bootstrapping data streams more
             // straightforward as all backing indices are required to have a timestamp field
             if (template.getDataStreamTemplate().isAllowCustomRouting()) {
-                mappings.add(0, new CompressedXContent(wrapMappingsIfNecessary(DEFAULT_TIMESTAMP_MAPPING_WITH_ROUTING, xContentRegistry)));
+                mappings.add(0, DEFAULT_TIMESTAMP_MAPPING_WITH_ROUTING);
             } else {
-                mappings.add(0, new CompressedXContent(wrapMappingsIfNecessary(DEFAULT_TIMESTAMP_MAPPING, xContentRegistry)));
+                mappings.add(0, DEFAULT_TIMESTAMP_MAPPING);
             }
         }
 
@@ -1187,17 +1182,9 @@ public class MetadataIndexTemplateService {
         if (indexName.startsWith(DataStream.BACKING_INDEX_PREFIX)) {
             // Only if template has data stream definition this should be added and
             // adding this template last, since _timestamp field should have highest precedence:
-            Optional.ofNullable(template.getDataStreamTemplate())
-                .map(ComposableIndexTemplate.DataStreamTemplate::getDataStreamMappingSnippet)
-                .map(mapping -> {
-                    try (XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent())) {
-                        builder.value(mapping);
-                        return new CompressedXContent(BytesReference.bytes(builder));
-                    } catch (IOException e) {
-                        throw new UncheckedIOException(e);
-                    }
-                })
-                .ifPresent(mappings::add);
+            if (template.getDataStreamTemplate() != null) {
+                mappings.add(ComposableIndexTemplate.DataStreamTemplate.DATA_STREAM_MAPPING_SNIPPET);
+            }
         }
         return Collections.unmodifiableList(mappings);
     }
@@ -1380,7 +1367,7 @@ public class MetadataIndexTemplateService {
             String indexName = DataStream.BACKING_INDEX_PREFIX + temporaryIndexName;
             // Parse mappings to ensure they are valid after being composed
 
-            List<CompressedXContent> mappings = collectMappings(stateWithIndex, templateName, indexName, xContentRegistry);
+            List<CompressedXContent> mappings = collectMappings(stateWithIndex, templateName, indexName);
             try {
                 MapperService mapperService = tempIndexService.mapperService();
                 for (CompressedXContent mapping : mappings) {
@@ -1399,19 +1386,10 @@ public class MetadataIndexTemplateService {
 
     private static void validateTemplate(
         Settings validateSettings,
-        String mappings,
+        CompressedXContent mappings,
         IndicesService indicesService,
         NamedXContentRegistry xContentRegistry
     ) throws Exception {
-        // First check to see if mappings are valid XContent
-        if (mappings != null) {
-            try {
-                new CompressedXContent(mappings);
-            } catch (Exception e) {
-                throw new MapperParsingException("Failed to parse mapping: {}", e, mappings);
-            }
-        }
-
         // Hard to validate settings if they're non-existent, so used empty ones if none were provided
         Settings settings = validateSettings;
         if (settings == null) {
@@ -1592,7 +1570,7 @@ public class MetadataIndexTemplateService {
         Integer version;
         List<String> indexPatterns;
         Settings settings = Settings.EMPTY;
-        String mappings = null;
+        CompressedXContent mappings = null;
         List<Alias> aliases = new ArrayList<>();
 
         TimeValue masterTimeout = MasterNodeRequest.DEFAULT_MASTER_NODE_TIMEOUT;
@@ -1622,7 +1600,7 @@ public class MetadataIndexTemplateService {
             return this;
         }
 
-        public PutRequest mappings(String mappings) {
+        public PutRequest mappings(CompressedXContent mappings) {
             this.mappings = mappings;
             return this;
         }

+ 6 - 3
server/src/main/java/org/elasticsearch/common/compress/CompressedXContent.java

@@ -19,7 +19,6 @@ import org.elasticsearch.core.Releasable;
 import org.elasticsearch.xcontent.ToXContent;
 import org.elasticsearch.xcontent.XContentBuilder;
 import org.elasticsearch.xcontent.XContentFactory;
-import org.elasticsearch.xcontent.XContentType;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -82,14 +81,18 @@ public final class CompressedXContent {
         assertConsistent();
     }
 
+    public CompressedXContent(ToXContent xcontent) throws IOException {
+        this(xcontent, ToXContent.EMPTY_PARAMS);
+    }
+
     /**
      * Create a {@link CompressedXContent} out of a {@link ToXContent} instance.
      */
-    public CompressedXContent(ToXContent xcontent, XContentType type, ToXContent.Params params) throws IOException {
+    public CompressedXContent(ToXContent xcontent, ToXContent.Params params) throws IOException {
         BytesStreamOutput bStream = new BytesStreamOutput();
         CRC32 crc32 = new CRC32();
         OutputStream checkedStream = new CheckedOutputStream(CompressorFactory.COMPRESSOR.threadLocalOutputStream(bStream), crc32);
-        try (XContentBuilder builder = XContentFactory.contentBuilder(type, checkedStream)) {
+        try (XContentBuilder builder = XContentFactory.jsonBuilder(checkedStream)) {
             if (xcontent.isFragment()) {
                 builder.startObject();
             }

+ 23 - 2
server/src/main/java/org/elasticsearch/index/mapper/MapperService.java

@@ -13,6 +13,7 @@ import org.elasticsearch.cluster.metadata.IndexMetadata;
 import org.elasticsearch.cluster.metadata.MappingMetadata;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.compress.CompressedXContent;
+import org.elasticsearch.common.compress.CompressorFactory;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Setting.Property;
 import org.elasticsearch.common.settings.Settings;
@@ -35,10 +36,12 @@ import org.elasticsearch.xcontent.NamedXContentRegistry;
 import org.elasticsearch.xcontent.ToXContent;
 import org.elasticsearch.xcontent.XContentFactory;
 import org.elasticsearch.xcontent.XContentParser;
+import org.elasticsearch.xcontent.XContentParserConfiguration;
 import org.elasticsearch.xcontent.XContentType;
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -229,6 +232,24 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
         }
     }
 
+    /**
+     * Parses the mappings (formatted as JSON) into a map
+     */
+    public static Map<String, Object> parseMapping(NamedXContentRegistry xContentRegistry, CompressedXContent mappingSource)
+        throws IOException {
+        try (
+            InputStream in = CompressorFactory.COMPRESSOR.threadLocalInputStream(mappingSource.compressedReference().streamInput());
+            XContentParser parser = XContentType.JSON.xContent()
+                .createParser(
+                    XContentParserConfiguration.EMPTY.withRegistry(xContentRegistry)
+                        .withDeprecationHandler(LoggingDeprecationHandler.INSTANCE),
+                    in
+                )
+        ) {
+            return parser.map();
+        }
+    }
+
     /**
      * Update local mapping by applying the incoming mapping that have already been merged with the current one on the master
      */
@@ -269,13 +290,13 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
         ToXContent.MapParams params = new ToXContent.MapParams(Collections.singletonMap(RootObjectMapper.TOXCONTENT_SKIP_RUNTIME, "true"));
         CompressedXContent mergedMappingSource;
         try {
-            mergedMappingSource = new CompressedXContent(mergedMapping, XContentType.JSON, params);
+            mergedMappingSource = new CompressedXContent(mergedMapping, params);
         } catch (Exception e) {
             throw new AssertionError("failed to serialize source for type [" + type + "]", e);
         }
         CompressedXContent incomingMappingSource;
         try {
-            incomingMappingSource = new CompressedXContent(incomingMapping, XContentType.JSON, params);
+            incomingMappingSource = new CompressedXContent(incomingMapping, params);
         } catch (Exception e) {
             throw new AssertionError("failed to serialize source for type [" + type + "]", e);
         }

+ 1 - 2
server/src/main/java/org/elasticsearch/index/mapper/Mapping.java

@@ -17,7 +17,6 @@ import org.elasticsearch.xcontent.ToXContent;
 import org.elasticsearch.xcontent.ToXContentFragment;
 import org.elasticsearch.xcontent.XContentBuilder;
 import org.elasticsearch.xcontent.XContentFactory;
-import org.elasticsearch.xcontent.XContentType;
 
 import java.io.IOException;
 import java.io.UncheckedIOException;
@@ -74,7 +73,7 @@ public final class Mapping implements ToXContentFragment {
      */
     public CompressedXContent toCompressedXContent() {
         try {
-            return new CompressedXContent(this, XContentType.JSON, ToXContent.EMPTY_PARAMS);
+            return new CompressedXContent(this);
         } catch (Exception e) {
             throw new ElasticsearchGenerationException("failed to serialize source for type [" + root.name() + "]", e);
         }

+ 21 - 55
server/src/test/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateServiceTests.java

@@ -18,7 +18,6 @@ import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService.PutRequest;
 import org.elasticsearch.cluster.service.ClusterService;
-import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.collect.ImmutableOpenMap;
 import org.elasticsearch.common.compress.CompressedXContent;
 import org.elasticsearch.common.settings.IndexScopedSettings;
@@ -36,8 +35,8 @@ import org.elasticsearch.test.ClusterServiceUtils;
 import org.elasticsearch.test.ESSingleNodeTestCase;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.xcontent.NamedXContentRegistry;
-import org.elasticsearch.xcontent.XContentFactory;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -74,7 +73,7 @@ import static org.mockito.Mockito.mock;
 
 public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
 
-    public void testLegacyNoopUpdate() {
+    public void testLegacyNoopUpdate() throws IOException {
         ClusterState state = ClusterState.EMPTY_STATE;
         PutRequest pr = new PutRequest("api", "id");
         pr.patterns(Arrays.asList("foo", "bar"));
@@ -82,7 +81,7 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
             pr.settings(Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 3).build());
         }
         if (randomBoolean()) {
-            pr.mappings("{}");
+            pr.mappings(new CompressedXContent("{}"));
         }
         if (randomBoolean()) {
             pr.aliases(Collections.singleton(new Alias("alias")));
@@ -190,10 +189,8 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
         PutRequest request = new PutRequest("api", "validate_template");
         request.patterns(singletonList("te*"));
         request.mappings(
-            Strings.toString(
-                XContentFactory.jsonBuilder()
-                    .startObject()
-                    .startObject("_doc")
+            new CompressedXContent(
+                (builder, params) -> builder.startObject("_doc")
                     .startObject("properties")
                     .startObject("field2")
                     .field("type", "text")
@@ -201,7 +198,6 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
                     .endObject()
                     .endObject()
                     .endObject()
-                    .endObject()
             )
         );
 
@@ -211,22 +207,11 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
         assertThat(errors.get(0).getMessage(), containsString("analyzer [custom_1] has not been configured in mappings"));
     }
 
-    public void testBrokenMapping() throws Exception {
-        PutRequest request = new PutRequest("api", "broken_mapping");
-        request.patterns(singletonList("te*"));
-        request.mappings("abcde");
-
-        List<Throwable> errors = putTemplateDetail(request);
-        assertThat(errors.size(), equalTo(1));
-        assertThat(errors.get(0), instanceOf(MapperParsingException.class));
-        assertThat(errors.get(0).getMessage(), containsString("Failed to parse mapping"));
-    }
-
     public void testAliasInvalidFilterInvalidJson() throws Exception {
         // invalid json: put index template fails
         PutRequest request = new PutRequest("api", "blank_mapping");
         request.patterns(singletonList("te*"));
-        request.mappings("{}");
+        request.mappings(new CompressedXContent("{}"));
         Set<Alias> aliases = new HashSet<>();
         aliases.add(new Alias("invalid_alias").filter("abcde"));
         request.aliases(aliases);
@@ -242,7 +227,7 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
         final String aliasName = "alias_with_settings";
         PutRequest request = new PutRequest("api", templateName);
         request.patterns(singletonList("te*"));
-        request.mappings("{}");
+        request.mappings(new CompressedXContent("{}"));
         Alias alias = new Alias(aliasName).filter(randomBoolean() ? null : "{\"term\":{\"user_id\":12}}")
             .indexRouting(randomBoolean() ? null : "route1")
             .searchRouting(randomBoolean() ? null : "route2")
@@ -1176,18 +1161,13 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
         );
         state = service.addIndexTemplateV2(state, true, "my-template", it);
 
-        List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(
-            state,
-            "my-template",
-            "my-index",
-            xContentRegistry()
-        );
+        List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(state, "my-template", "my-index");
 
         assertNotNull(mappings);
         assertThat(mappings.size(), equalTo(3));
         List<Map<String, Object>> parsedMappings = mappings.stream().map(m -> {
             try {
-                return MapperService.parseMapping(new NamedXContentRegistry(List.of()), m.string());
+                return MapperService.parseMapping(NamedXContentRegistry.EMPTY, m);
             } catch (Exception e) {
                 logger.error(e);
                 fail("failed to parse mappings: " + m.string());
@@ -1269,18 +1249,13 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
         );
         state = service.addIndexTemplateV2(state, true, "my-template", it);
 
-        List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(
-            state,
-            "my-template",
-            "my-index",
-            xContentRegistry()
-        );
+        List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(state, "my-template", "my-index");
 
         assertNotNull(mappings);
         assertThat(mappings.size(), equalTo(3));
         List<Map<String, Object>> parsedMappings = mappings.stream().map(m -> {
             try {
-                return MapperService.parseMapping(new NamedXContentRegistry(List.of()), m.string());
+                return MapperService.parseMapping(NamedXContentRegistry.EMPTY, m);
             } catch (Exception e) {
                 logger.error(e);
                 fail("failed to parse mappings: " + m.string());
@@ -1344,15 +1319,14 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
             List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(
                 state,
                 "logs-data-stream-template",
-                DataStream.getDefaultBackingIndexName("logs", 1L),
-                xContentRegistry()
+                DataStream.getDefaultBackingIndexName("logs", 1L)
             );
 
             assertNotNull(mappings);
             assertThat(mappings.size(), equalTo(4));
             List<Map<String, Object>> parsedMappings = mappings.stream().map(m -> {
                 try {
-                    return MapperService.parseMapping(new NamedXContentRegistry(List.of()), m.string());
+                    return MapperService.parseMapping(NamedXContentRegistry.EMPTY, m);
                 } catch (Exception e) {
                     logger.error(e);
                     fail("failed to parse mappings: " + m.string());
@@ -1394,18 +1368,13 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
             );
             state = service.addIndexTemplateV2(state, true, "timeseries-template", it);
 
-            List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(
-                state,
-                "timeseries-template",
-                "timeseries",
-                xContentRegistry()
-            );
+            List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(state, "timeseries-template", "timeseries");
 
             assertNotNull(mappings);
             assertThat(mappings.size(), equalTo(2));
             List<Map<String, Object>> parsedMappings = mappings.stream().map(m -> {
                 try {
-                    return MapperService.parseMapping(new NamedXContentRegistry(List.of()), m.string());
+                    return MapperService.parseMapping(NamedXContentRegistry.EMPTY, m);
                 } catch (Exception e) {
                     logger.error(e);
                     fail("failed to parse mappings: " + m.string());
@@ -1421,15 +1390,14 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
             mappings = MetadataIndexTemplateService.collectMappings(
                 state,
                 "timeseries-template",
-                DataStream.getDefaultBackingIndexName("timeseries", 1L),
-                xContentRegistry()
+                DataStream.getDefaultBackingIndexName("timeseries", 1L)
             );
 
             assertNotNull(mappings);
             assertThat(mappings.size(), equalTo(2));
             parsedMappings = mappings.stream().map(m -> {
                 try {
-                    return MapperService.parseMapping(new NamedXContentRegistry(List.of()), m.string());
+                    return MapperService.parseMapping(NamedXContentRegistry.EMPTY, m);
                 } catch (Exception e) {
                     logger.error(e);
                     fail("failed to parse mappings: " + m.string());
@@ -1482,15 +1450,14 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
             List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(
                 state,
                 "logs-template",
-                DataStream.getDefaultBackingIndexName("logs", 1L),
-                xContentRegistry()
+                DataStream.getDefaultBackingIndexName("logs", 1L)
             );
 
             assertNotNull(mappings);
             assertThat(mappings.size(), equalTo(3));
             List<Map<String, Object>> parsedMappings = mappings.stream().map(m -> {
                 try {
-                    return MapperService.parseMapping(new NamedXContentRegistry(List.of()), m.string());
+                    return MapperService.parseMapping(NamedXContentRegistry.EMPTY, m);
                 } catch (Exception e) {
                     logger.error(e);
                     fail("failed to parse mappings: " + m.string());
@@ -1537,15 +1504,14 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
             List<CompressedXContent> mappings = MetadataIndexTemplateService.collectMappings(
                 state,
                 "timeseries-template",
-                DataStream.getDefaultBackingIndexName("timeseries-template", 1L),
-                xContentRegistry()
+                DataStream.getDefaultBackingIndexName("timeseries-template", 1L)
             );
 
             assertNotNull(mappings);
             assertThat(mappings.size(), equalTo(3));
             List<Map<String, Object>> parsedMappings = mappings.stream().map(m -> {
                 try {
-                    return MapperService.parseMapping(new NamedXContentRegistry(List.of()), m.string());
+                    return MapperService.parseMapping(NamedXContentRegistry.EMPTY, m);
                 } catch (Exception e) {
                     logger.error(e);
                     fail("failed to parse mappings: " + m.string());

+ 5 - 13
server/src/test/java/org/elasticsearch/common/compress/DeflateCompressedXContentTests.java

@@ -12,11 +12,9 @@ import org.apache.lucene.util.TestUtil;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.test.ESTestCase;
-import org.elasticsearch.xcontent.ToXContent;
 import org.elasticsearch.xcontent.ToXContentFragment;
 import org.elasticsearch.xcontent.ToXContentObject;
 import org.elasticsearch.xcontent.XContentFactory;
-import org.elasticsearch.xcontent.XContentType;
 import org.junit.Assert;
 
 import java.io.IOException;
@@ -100,13 +98,13 @@ public class DeflateCompressedXContentTests extends ESTestCase {
             builder.endObject();
             return builder;
         };
-        CompressedXContent compressedXContent = new CompressedXContent(toXContentObject, XContentType.JSON, ToXContent.EMPTY_PARAMS);
+        CompressedXContent compressedXContent = new CompressedXContent(toXContentObject);
         assertEquals("{}", compressedXContent.string());
     }
 
     public void testToXContentFragment() throws IOException {
         ToXContentFragment toXContentFragment = (builder, params) -> builder.field("field", "value");
-        CompressedXContent compressedXContent = new CompressedXContent(toXContentFragment, XContentType.JSON, ToXContent.EMPTY_PARAMS);
+        CompressedXContent compressedXContent = new CompressedXContent(toXContentFragment);
         assertEquals("{\"field\":\"value\"}", compressedXContent.string());
     }
 
@@ -118,9 +116,7 @@ public class DeflateCompressedXContentTests extends ESTestCase {
         );
         final CompressedXContent one = new CompressedXContent(jsonDirect);
         final CompressedXContent sameAsOne = new CompressedXContent(
-            (builder, params) -> builder.stringListField("arr", Arrays.asList(randomJSON)),
-            XContentType.JSON,
-            ToXContent.EMPTY_PARAMS
+            (builder, params) -> builder.stringListField("arr", Arrays.asList(randomJSON))
         );
         assertFalse(Arrays.equals(one.compressed(), sameAsOne.compressed()));
         assertEquals(one, sameAsOne);
@@ -133,14 +129,10 @@ public class DeflateCompressedXContentTests extends ESTestCase {
             () -> generateRandomStringArray(randomIntBetween(1, 1000), randomIntBetween(1, 512), false, true)
         );
         final CompressedXContent one = new CompressedXContent(
-            (builder, params) -> builder.stringListField("arr", Arrays.asList(randomJSON1)),
-            XContentType.JSON,
-            ToXContent.EMPTY_PARAMS
+            (builder, params) -> builder.stringListField("arr", Arrays.asList(randomJSON1))
         );
         final CompressedXContent two = new CompressedXContent(
-            (builder, params) -> builder.stringListField("arr", Arrays.asList(randomJSON2)),
-            XContentType.JSON,
-            ToXContent.EMPTY_PARAMS
+            (builder, params) -> builder.stringListField("arr", Arrays.asList(randomJSON2))
         );
         assertFalse(CompressedXContent.equalsWhenUncompressed(one.compressed(), two.compressed()));
     }