/* * 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.cluster.metadata; import org.opensearch.OpenSearchParseException; import org.opensearch.cluster.AbstractDiffable; import org.opensearch.cluster.Diff; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.compress.CompressedXContent; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.index.mapper.DocumentMapper; import org.opensearch.index.mapper.MapperService; import java.io.IOException; import java.io.UncheckedIOException; import java.util.Collections; import java.util.Map; import java.util.Objects; import static org.opensearch.common.xcontent.support.XContentMapValues.nodeBooleanValue; /** * Mapping configuration for a type. * * @opensearch.internal */ public class MappingMetadata extends AbstractDiffable { public static final MappingMetadata EMPTY_MAPPINGS = new MappingMetadata(MapperService.SINGLE_MAPPING_NAME, Collections.emptyMap()); private final String type; private final CompressedXContent source; private final boolean routingRequired; public MappingMetadata(DocumentMapper docMapper) { this.type = docMapper.type(); this.source = docMapper.mappingSource(); this.routingRequired = docMapper.routingFieldMapper().required(); } @SuppressWarnings("unchecked") public MappingMetadata(CompressedXContent mapping) { this.source = mapping; Map mappingMap = XContentHelper.convertToMap(mapping.compressedReference(), true).v2(); if (mappingMap.size() != 1) { throw new IllegalStateException("Can't derive type from mapping, no root type: " + mapping.string()); } this.type = mappingMap.keySet().iterator().next(); this.routingRequired = isRoutingRequired((Map) mappingMap.get(this.type)); } @SuppressWarnings("unchecked") public MappingMetadata(String type, Map mapping) { this.type = type; try { XContentBuilder mappingBuilder = XContentFactory.jsonBuilder().map(mapping); this.source = new CompressedXContent(BytesReference.bytes(mappingBuilder)); } catch (IOException e) { throw new UncheckedIOException(e); // XContent exception, should never happen } Map withoutType = mapping; if (mapping.size() == 1 && mapping.containsKey(type)) { withoutType = (Map) mapping.get(type); } this.routingRequired = isRoutingRequired(withoutType); } @SuppressWarnings("unchecked") private boolean isRoutingRequired(Map withoutType) { boolean required = false; if (withoutType.containsKey("_routing")) { Map routingNode = (Map) withoutType.get("_routing"); for (Map.Entry entry : routingNode.entrySet()) { String fieldName = entry.getKey(); Object fieldNode = entry.getValue(); if (fieldName.equals("required")) { try { required = nodeBooleanValue(fieldNode); } catch (IllegalArgumentException ex) { throw new IllegalArgumentException( "Failed to create mapping for type [" + this.type() + "]. " + "Illegal value in field [_routing.required].", ex ); } } } } return required; } public String type() { return this.type; } public CompressedXContent source() { return this.source; } /** * Converts the serialized compressed form of the mappings into a parsed map. */ public Map sourceAsMap() throws OpenSearchParseException { Map mapping = XContentHelper.convertToMap(source.compressedReference(), true).v2(); if (mapping.size() == 1 && mapping.containsKey(type())) { // the type name is the root value, reduce it mapping = (Map) mapping.get(type()); } return mapping; } /** * Converts the serialized compressed form of the mappings into a parsed map. */ public Map getSourceAsMap() throws OpenSearchParseException { return sourceAsMap(); } public boolean routingRequired() { return this.routingRequired; } @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(type()); source().writeTo(out); // routing out.writeBoolean(routingRequired); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MappingMetadata that = (MappingMetadata) o; if (!Objects.equals(this.routingRequired, that.routingRequired)) return false; if (!source.equals(that.source)) return false; if (!type.equals(that.type)) return false; return true; } @Override public int hashCode() { return Objects.hash(type, source, routingRequired); } public MappingMetadata(StreamInput in) throws IOException { type = in.readString(); source = CompressedXContent.readCompressedString(in); // routing routingRequired = in.readBoolean(); } public static Diff readDiffFrom(StreamInput in) throws IOException { return readDiffFrom(MappingMetadata::new, in); } }