1919import org .elasticsearch .common .io .stream .StreamOutput ;
2020import org .elasticsearch .common .io .stream .Writeable ;
2121import org .elasticsearch .common .settings .Settings ;
22+ import org .elasticsearch .common .xcontent .XContentHelper ;
2223import org .elasticsearch .core .Nullable ;
2324import org .elasticsearch .index .mapper .DataStreamTimestampFieldMapper ;
2425import org .elasticsearch .index .mapper .MapperService ;
26+ import org .elasticsearch .index .mapper .Mapping ;
2527import org .elasticsearch .xcontent .ConstructingObjectParser ;
2628import org .elasticsearch .xcontent .ParseField ;
2729import org .elasticsearch .xcontent .ToXContentObject ;
2830import org .elasticsearch .xcontent .XContentBuilder ;
31+ import org .elasticsearch .xcontent .XContentFactory ;
2932import org .elasticsearch .xcontent .XContentParser ;
33+ import org .elasticsearch .xcontent .XContentParserConfiguration ;
34+ import org .elasticsearch .xcontent .XContentType ;
3035
3136import java .io .IOException ;
3237import java .util .ArrayList ;
3338import java .util .Collections ;
39+ import java .util .HashMap ;
3440import java .util .List ;
3541import java .util .Map ;
3642import java .util .Objects ;
@@ -51,6 +57,14 @@ public class ComposableIndexTemplate implements SimpleDiffable<ComposableIndexTe
5157 private static final ParseField ALLOW_AUTO_CREATE = new ParseField ("allow_auto_create" );
5258 private static final ParseField IGNORE_MISSING_COMPONENT_TEMPLATES = new ParseField ("ignore_missing_component_templates" );
5359 private static final ParseField DEPRECATED = new ParseField ("deprecated" );
60+ public static final CompressedXContent EMPTY_MAPPINGS ;
61+ static {
62+ try {
63+ EMPTY_MAPPINGS = new CompressedXContent (Map .of ());
64+ } catch (IOException e ) {
65+ throw new RuntimeException (e );
66+ }
67+ }
5468
5569 @ SuppressWarnings ("unchecked" )
5670 public static final ConstructingObjectParser <ComposableIndexTemplate , Void > PARSER = new ConstructingObjectParser <>(
@@ -338,6 +352,64 @@ public ComposableIndexTemplate mergeSettings(Settings settings) {
338352 return mergedIndexTemplateBuilder .build ();
339353 }
340354
355+ public ComposableIndexTemplate mergeMappings (CompressedXContent mappings ) throws IOException {
356+ Objects .requireNonNull (mappings );
357+ if (Mapping .EMPTY .toCompressedXContent ().equals (mappings ) && this .template () != null && this .template ().mappings () != null ) {
358+ return this ;
359+ }
360+ ComposableIndexTemplate .Builder mergedIndexTemplateBuilder = this .toBuilder ();
361+ Template .Builder mergedTemplateBuilder ;
362+ CompressedXContent templateMappings ;
363+ if (this .template () == null ) {
364+ mergedTemplateBuilder = Template .builder ();
365+ templateMappings = null ;
366+ } else {
367+ mergedTemplateBuilder = Template .builder (this .template ());
368+ templateMappings = this .template ().mappings ();
369+ }
370+ mergedTemplateBuilder .mappings (templateMappings == null ? mappings : merge (templateMappings , mappings ));
371+ mergedIndexTemplateBuilder .template (mergedTemplateBuilder );
372+ return mergedIndexTemplateBuilder .build ();
373+ }
374+
375+ @ SuppressWarnings ("unchecked" )
376+ private CompressedXContent merge (CompressedXContent originalMapping , CompressedXContent mappingAddition ) throws IOException {
377+ Map <String , Object > mappingAdditionMap = XContentHelper .convertToMap (mappingAddition .uncompressed (), true , XContentType .JSON ).v2 ();
378+ Map <String , Object > combinedMappingMap = new HashMap <>();
379+ if (originalMapping != null ) {
380+ Map <String , Object > originalMappingMap = XContentHelper .convertToMap (originalMapping .uncompressed (), true , XContentType .JSON )
381+ .v2 ();
382+ if (originalMappingMap .containsKey (MapperService .SINGLE_MAPPING_NAME )) {
383+ combinedMappingMap .putAll ((Map <String , ?>) originalMappingMap .get (MapperService .SINGLE_MAPPING_NAME ));
384+ } else {
385+ combinedMappingMap .putAll (originalMappingMap );
386+ }
387+ }
388+ XContentHelper .update (combinedMappingMap , mappingAdditionMap , true );
389+ return convertMappingMapToXContent (combinedMappingMap );
390+ }
391+
392+ private static CompressedXContent convertMappingMapToXContent (Map <String , Object > rawAdditionalMapping ) throws IOException {
393+ CompressedXContent compressedXContent ;
394+ if (rawAdditionalMapping .isEmpty ()) {
395+ compressedXContent = EMPTY_MAPPINGS ;
396+ } else {
397+ try (var parser = XContentHelper .mapToXContentParser (XContentParserConfiguration .EMPTY , rawAdditionalMapping )) {
398+ compressedXContent = mappingFromXContent (parser );
399+ }
400+ }
401+ return compressedXContent ;
402+ }
403+
404+ private static CompressedXContent mappingFromXContent (XContentParser parser ) throws IOException {
405+ XContentParser .Token token = parser .nextToken ();
406+ if (token == XContentParser .Token .START_OBJECT ) {
407+ return new CompressedXContent (Strings .toString (XContentFactory .jsonBuilder ().map (parser .mapOrdered ())));
408+ } else {
409+ throw new IllegalArgumentException ("Unexpected token: " + token );
410+ }
411+ }
412+
341413 @ Override
342414 public int hashCode () {
343415 return Objects .hash (
0 commit comments