/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.index.mapper; import org.opensearch.Version; import org.opensearch.common.Strings; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.ToXContentFragment; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.index.mapper.MapperService.MergeReason; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import static java.util.Collections.emptyMap; import static java.util.Collections.unmodifiableMap; /** * Wrapper around everything that defines a mapping, without references to * utility classes like MapperService, ... * * @opensearch.internal */ public final class Mapping implements ToXContentFragment { final Version indexCreated; final RootObjectMapper root; final MetadataFieldMapper[] metadataMappers; final Map, MetadataFieldMapper> metadataMappersMap; final Map metadataMappersByName; final Map meta; public Mapping( Version indexCreated, RootObjectMapper rootObjectMapper, MetadataFieldMapper[] metadataMappers, Map meta ) { this.indexCreated = indexCreated; this.metadataMappers = metadataMappers; Map, MetadataFieldMapper> metadataMappersMap = new HashMap<>(); Map metadataMappersByName = new HashMap<>(); for (MetadataFieldMapper metadataMapper : metadataMappers) { metadataMappersMap.put(metadataMapper.getClass(), metadataMapper); metadataMappersByName.put(metadataMapper.name(), metadataMapper); } this.root = rootObjectMapper; // keep root mappers sorted for consistent serialization Arrays.sort(metadataMappers, new Comparator() { @Override public int compare(Mapper o1, Mapper o2) { return o1.name().compareTo(o2.name()); } }); this.metadataMappersMap = unmodifiableMap(metadataMappersMap); this.metadataMappersByName = unmodifiableMap(metadataMappersByName); this.meta = meta; } /** Return the root object mapper. */ public RootObjectMapper root() { return root; } public void validate(MappingLookup mappers) { for (MetadataFieldMapper metadataFieldMapper : metadataMappers) { metadataFieldMapper.validate(mappers); } root.validate(mappers); } /** * Generate a mapping update for the given root object mapper. */ public Mapping mappingUpdate(Mapper rootObjectMapper) { return new Mapping(indexCreated, (RootObjectMapper) rootObjectMapper, metadataMappers, meta); } /** Get the root mapper with the given class. */ @SuppressWarnings("unchecked") public T metadataMapper(Class clazz) { return (T) metadataMappersMap.get(clazz); } /** * Merges a new mapping into the existing one. * * @param mergeWith the new mapping to merge into this one. * @param reason the reason this merge was initiated. * @return the resulting merged mapping. */ public Mapping merge(Mapping mergeWith, MergeReason reason) { RootObjectMapper mergedRoot = root.merge(mergeWith.root, reason); // When merging metadata fields as part of applying an index template, new field definitions // completely overwrite existing ones instead of being merged. This behavior matches how we // merge leaf fields in the 'properties' section of the mapping. Map, MetadataFieldMapper> mergedMetadataMappers = new HashMap<>(metadataMappersMap); for (MetadataFieldMapper metaMergeWith : mergeWith.metadataMappers) { MetadataFieldMapper mergeInto = mergedMetadataMappers.get(metaMergeWith.getClass()); MetadataFieldMapper merged; if (mergeInto == null || reason == MergeReason.INDEX_TEMPLATE) { merged = metaMergeWith; } else { merged = (MetadataFieldMapper) mergeInto.merge(metaMergeWith); } mergedMetadataMappers.put(merged.getClass(), merged); } // If we are merging the _meta object as part of applying an index template, then the new object // is deep-merged into the existing one to allow individual keys to be added or overwritten. For // standard mapping updates, the new _meta object completely replaces the old one. Map mergedMeta; if (mergeWith.meta == null) { mergedMeta = meta; } else if (meta == null || reason != MergeReason.INDEX_TEMPLATE) { mergedMeta = mergeWith.meta; } else { mergedMeta = new HashMap<>(mergeWith.meta); XContentHelper.mergeDefaults(mergedMeta, meta); } return new Mapping(indexCreated, mergedRoot, mergedMetadataMappers.values().toArray(new MetadataFieldMapper[0]), mergedMeta); } public MetadataFieldMapper getMetadataMapper(String mapperName) { return metadataMappersByName.get(mapperName); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { root.toXContent(builder, params, new ToXContent() { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { if (meta != null) { builder.field("_meta", meta); } for (Mapper mapper : metadataMappers) { mapper.toXContent(builder, params); } return builder; } }); return builder; } @Override public String toString() { try { XContentBuilder builder = XContentFactory.jsonBuilder().startObject(); toXContent(builder, new ToXContent.MapParams(emptyMap())); return Strings.toString(builder.endObject()); } catch (IOException bogus) { throw new UncheckedIOException(bogus); } } }