All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensearch.index.mapper.AbstractPointGeometryFieldMapper Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 * 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.apache.lucene.document.FieldType;
import org.opensearch.OpenSearchParseException;
import org.opensearch.common.CheckedBiFunction;
import org.opensearch.common.Explicit;
import org.opensearch.common.geo.GeoPoint;
import org.opensearch.common.geo.GeometryFormat;
import org.opensearch.common.geo.GeometryParser;
import org.opensearch.common.xcontent.LoggingDeprecationHandler;
import org.opensearch.core.ParseField;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.geometry.Geometry;
import org.opensearch.geometry.Point;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import static org.opensearch.index.mapper.TypeParsers.parseField;

/**
 * Base class for spatial fields that only support indexing points
 *
 * @opensearch.internal
 */
public abstract class AbstractPointGeometryFieldMapper extends AbstractGeometryFieldMapper {

    /**
     * Common parameter names for the base point geometry field mapper
     *
     * @opensearch.internal
     */
    public static class Names extends AbstractGeometryFieldMapper.Names {
        public static final ParseField NULL_VALUE = new ParseField("null_value");
    }

    public static final FieldType DEFAULT_FIELD_TYPE = new FieldType();
    static {
        DEFAULT_FIELD_TYPE.setDimensions(2, Integer.BYTES);
        DEFAULT_FIELD_TYPE.setStored(false);
        DEFAULT_FIELD_TYPE.freeze();
    }

    /**
     * Base builder for the base point geometry field mapper
     *
     * @opensearch.internal
     */
    public abstract static class Builder, FT extends AbstractPointGeometryFieldType> extends
        AbstractGeometryFieldMapper.Builder {

        protected ParsedPoint nullValue;

        public Builder(String name, FieldType fieldType) {
            super(name, fieldType);
        }

        public void setNullValue(ParsedPoint nullValue) {
            this.nullValue = nullValue;
        }

        public abstract AbstractPointGeometryFieldMapper build(
            BuilderContext context,
            String simpleName,
            FieldType fieldType,
            MultiFields multiFields,
            Explicit ignoreMalformed,
            Explicit ignoreZValue,
            ParsedPoint nullValue,
            CopyTo copyTo
        );

        @Override
        public AbstractPointGeometryFieldMapper build(BuilderContext context) {
            return build(
                context,
                name,
                fieldType,
                multiFieldsBuilder.build(this, context),
                ignoreMalformed(context),
                ignoreZValue(context),
                nullValue,
                copyTo
            );
        }
    }

    /**
     * Base type parser for the base point geometry field mapper
     *
     * @opensearch.internal
     */
    public abstract static class TypeParser extends AbstractGeometryFieldMapper.TypeParser {
        protected abstract ParsedPoint parseNullValue(Object nullValue, boolean ignoreZValue, boolean ignoreMalformed);

        @Override
        public T parse(String name, Map node, Map params, ParserContext parserContext) {
            T builder = (T) (super.parse(name, node, params, parserContext));
            parseField(builder, name, node, parserContext);
            Object nullValue = null;
            for (Iterator> iterator = node.entrySet().iterator(); iterator.hasNext();) {
                Map.Entry entry = iterator.next();
                String propName = entry.getKey();
                Object propNode = entry.getValue();

                if (Names.NULL_VALUE.match(propName, LoggingDeprecationHandler.INSTANCE)) {
                    nullValue = propNode;
                    iterator.remove();
                }
            }

            if (nullValue != null) {
                builder.setNullValue(
                    parseNullValue(nullValue, (Boolean) builder.ignoreZValue().value(), (Boolean) builder.ignoreMalformed().value())
                );
            }

            return builder;
        }
    }

    ParsedPoint nullValue;

    /**
     * Base field type for the base point geometry field mapper
     *
     * @opensearch.internal
     */
    public abstract static class AbstractPointGeometryFieldType extends AbstractGeometryFieldType {
        protected AbstractPointGeometryFieldType(
            String name,
            boolean indexed,
            boolean stored,
            boolean hasDocValues,
            Map meta
        ) {
            super(name, indexed, stored, hasDocValues, true, meta);
        }
    }

    protected AbstractPointGeometryFieldMapper(
        String simpleName,
        FieldType fieldType,
        MappedFieldType mappedFieldType,
        MultiFields multiFields,
        Explicit ignoreMalformed,
        Explicit ignoreZValue,
        ParsedPoint nullValue,
        CopyTo copyTo
    ) {
        super(simpleName, fieldType, mappedFieldType, ignoreMalformed, ignoreZValue, multiFields, copyTo);
        this.nullValue = nullValue;
    }

    @Override
    public final boolean parsesArrayValue() {
        return true;
    }

    @Override
    protected void mergeOptions(FieldMapper other, List conflicts) {
        super.mergeOptions(other, conflicts);
        AbstractPointGeometryFieldMapper gpfm = (AbstractPointGeometryFieldMapper) other;
        // TODO make this un-updateable
        if (gpfm.nullValue != null) {
            this.nullValue = gpfm.nullValue;
        }
    }

    @Override
    public void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
        super.doXContentBody(builder, includeDefaults, params);
        if (nullValue != null || includeDefaults) {
            builder.field(Names.NULL_VALUE.getPreferredName(), nullValue);
        }
    }

    public ParsedPoint nullValue() {
        return nullValue;
    }

    /**
     * represents a Point that has been parsed by {@link PointParser}
     *
     * @opensearch.internal
     */
    public interface ParsedPoint {
        void validate(String fieldName);

        void normalize(String fieldName);

        void resetCoords(double x, double y);

        Point asGeometry();

        default boolean isNormalizable(double coord) {
            return Double.isNaN(coord) == false && Double.isInfinite(coord) == false;
        }
    }

    /**
     * A parser implementation that can parse the various point formats
     *
     * @opensearch.internal
     */
    public static class PointParser

extends Parser> { /** * Note that this parser is only used for formatting values. */ private final GeometryParser geometryParser; private final String field; private final Supplier

pointSupplier; private final CheckedBiFunction objectParser; private final P nullValue; private final boolean ignoreZValue; private final boolean ignoreMalformed; public PointParser( String field, Supplier

pointSupplier, CheckedBiFunction objectParser, P nullValue, boolean ignoreZValue, boolean ignoreMalformed ) { this.field = field; this.pointSupplier = pointSupplier; this.objectParser = objectParser; this.nullValue = nullValue; this.ignoreZValue = ignoreZValue; this.ignoreMalformed = ignoreMalformed; this.geometryParser = new GeometryParser(true, true, true); } private P process(P in) { if (ignoreMalformed == false) { in.validate(field); } else { in.normalize(field); } return in; } @Override public List

parse(XContentParser parser) throws IOException, ParseException { if (parser.currentToken() == XContentParser.Token.START_ARRAY) { XContentParser.Token token = parser.nextToken(); P point = pointSupplier.get(); ArrayList

points = new ArrayList<>(); if (token == XContentParser.Token.VALUE_NUMBER) { double x = parser.doubleValue(); parser.nextToken(); double y = parser.doubleValue(); token = parser.nextToken(); if (token == XContentParser.Token.VALUE_NUMBER) { GeoPoint.assertZValue(ignoreZValue, parser.doubleValue()); } else if (token != XContentParser.Token.END_ARRAY) { throw new OpenSearchParseException("field type does not accept > 3 dimensions"); } point.resetCoords(x, y); points.add(process(point)); } else { while (token != XContentParser.Token.END_ARRAY) { points.add(process(objectParser.apply(parser, point))); point = pointSupplier.get(); token = parser.nextToken(); } } return points; } else if (parser.currentToken() == XContentParser.Token.VALUE_NULL) { if (nullValue == null) { return null; } else { return Collections.singletonList(nullValue); } } else { return Collections.singletonList(process(objectParser.apply(parser, pointSupplier.get()))); } } @Override public Object format(List

points, String format) { List result = new ArrayList<>(); GeometryFormat geometryFormat = geometryParser.geometryFormat(format); for (ParsedPoint point : points) { Geometry geometry = point.asGeometry(); result.add(geometryFormat.toXContentAsObject(geometry)); } return result; } } }