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

org.elasticsearch.index.mapper.AbstractGeometryFieldMapper Maven / Gradle / Ivy

There is a newer version: 8.13.4
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */
package org.elasticsearch.index.mapper;

import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.Explicit;
import org.elasticsearch.common.geo.GeoJsonGeometryFormat;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.support.MapXContentParser;
import org.elasticsearch.index.analysis.NamedAnalyzer;
import org.elasticsearch.index.query.QueryShardContext;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * Base field mapper class for all spatial field types
 */
public abstract class AbstractGeometryFieldMapper extends FieldMapper {

    public static Parameter> ignoreMalformedParam(Function> initializer,
                                                                    boolean ignoreMalformedByDefault) {
        return Parameter.explicitBoolParam("ignore_malformed", true, initializer, ignoreMalformedByDefault);
    }

    public static Parameter> ignoreZValueParam(Function> initializer) {
        return Parameter.explicitBoolParam("ignore_z_value", true, initializer, true);
    }

    /**
     * Interface representing an preprocessor in geometry indexing pipeline
     */
    public interface Indexer {
        Processed prepareForIndexing(Parsed geometry);
        Class processedClass();
        List indexShape(ParseContext context, Processed shape);
    }

    /**
     * Interface representing parser in geometry indexing pipeline.
     */
    public abstract static class Parser {
        /**
         * Parse the given xContent value to an object of type {@link Parsed}. The value can be
         * in any supported format.
         */
        public abstract Parsed parse(XContentParser parser) throws IOException, ParseException;

        /**
         * Given a parsed value and a format string, formats the value into a plain Java object.
         *
         * Supported formats include 'geojson' and 'wkt'. The different formats are defined
         * as subclasses of {@link org.elasticsearch.common.geo.GeometryFormat}.
         */
        public abstract Object format(Parsed value, String format);

        /**
         * Parses the given value, then formats it according to the 'format' string.
         *
         * By default, this method simply parses the value using {@link Parser#parse}, then formats
         * it with {@link Parser#format}. However some {@link Parser} implementations override this
         * as they can avoid parsing the value if it is already in the right format.
         */
        public Object parseAndFormatObject(Object value, String format) {
            Parsed geometry;
            try (XContentParser parser = new MapXContentParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE,
                Collections.singletonMap("dummy_field", value), XContentType.JSON)) {
                parser.nextToken(); // start object
                parser.nextToken(); // field name
                parser.nextToken(); // field value
                geometry = parse(parser);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            } catch (ParseException e) {
                throw new RuntimeException(e);
            }
            return format(geometry, format);
        }
    }

    public abstract static class AbstractGeometryFieldType extends MappedFieldType {

        protected final Parser geometryParser;
        protected final boolean parsesArrayValue;

        protected AbstractGeometryFieldType(String name, boolean indexed, boolean stored, boolean hasDocValues,
                                            boolean parsesArrayValue, Parser geometryParser, Map meta) {
            super(name, indexed, stored, hasDocValues, TextSearchInfo.NONE, meta);
            this.parsesArrayValue = parsesArrayValue;
            this.geometryParser = geometryParser;
        }

        @Override
        public final Query termQuery(Object value, QueryShardContext context) {
            throw new IllegalArgumentException("Geometry fields do not support exact searching, use dedicated geometry queries instead: ["
                    + name() + "]");
        }

        @Override
        public final ValueFetcher valueFetcher(QueryShardContext context, String format) {
            String geoFormat = format != null ? format : GeoJsonGeometryFormat.NAME;

            Function valueParser = value -> geometryParser.parseAndFormatObject(value, geoFormat);
            if (parsesArrayValue) {
                return new ArraySourceValueFetcher(name(), context) {
                    @Override
                    protected Object parseSourceValue(Object value) {
                        return valueParser.apply(value);
                    }
                };
            } else {
                return new SourceValueFetcher(name(), context) {
                    @Override
                    protected Object parseSourceValue(Object value) {
                        return valueParser.apply(value);
                    }
                };
            }
        }
    }

    private final Explicit ignoreMalformed;
    private final Explicit ignoreZValue;
    private final Indexer indexer;
    private final Parser parser;

    protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType mappedFieldType,
                                          Map indexAnalyzers,
                                          Explicit ignoreMalformed, Explicit ignoreZValue,
                                          MultiFields multiFields, CopyTo copyTo,
                                          Indexer indexer, Parser parser) {
        super(simpleName, mappedFieldType, indexAnalyzers, multiFields, copyTo);
        this.ignoreMalformed = ignoreMalformed;
        this.ignoreZValue = ignoreZValue;
        this.indexer = indexer;
        this.parser = parser;
    }

    protected AbstractGeometryFieldMapper(String simpleName, MappedFieldType mappedFieldType,
                                          Explicit ignoreMalformed, Explicit ignoreZValue,
                                          MultiFields multiFields, CopyTo copyTo,
                                          Indexer indexer, Parser parser) {
        this(simpleName, mappedFieldType, Collections.emptyMap(), ignoreMalformed, ignoreZValue, multiFields, copyTo, indexer, parser);
    }

    @Override
    public AbstractGeometryFieldType fieldType() {
        return (AbstractGeometryFieldType) mappedFieldType;
    }

    @Override
    protected void parseCreateField(ParseContext context) throws IOException {
        throw new UnsupportedOperationException("Parsing is implemented in parse(), this method should NEVER be called");
    }

    protected abstract void addStoredFields(ParseContext context, Processed geometry);
    protected abstract void addDocValuesFields(String name, Processed geometry, List fields, ParseContext context);
    protected abstract void addMultiFields(ParseContext context, Processed geometry) throws IOException;

    /** parsing logic for geometry indexing */
    @Override
    public void parse(ParseContext context) throws IOException {
        MappedFieldType mappedFieldType = fieldType();

        try {
            Processed shape = context.parseExternalValue(indexer.processedClass());
            if (shape == null) {
                Parsed geometry = parser.parse(context.parser());
                if (geometry == null) {
                    return;
                }
                shape = indexer.prepareForIndexing(geometry);
            }

            List fields = new ArrayList<>();
            if (mappedFieldType.isSearchable() || mappedFieldType.hasDocValues()) {
                fields.addAll(indexer.indexShape(context, shape));
            }

            // indexed:
            List indexedFields = new ArrayList<>();
            if (mappedFieldType.isSearchable()) {
                indexedFields.addAll(fields);
            }
            // stored:
            if (fieldType().isStored()) {
                addStoredFields(context, shape);
            }
            // docValues:
            if (fieldType().hasDocValues()) {
                addDocValuesFields(mappedFieldType.name(), shape, fields, context);
            } else if (fieldType().isStored() || fieldType().isSearchable()) {
                createFieldNamesField(context);
            }

            // add the indexed fields to the doc:
            for (IndexableField field : indexedFields) {
                context.doc().add(field);
            }

            // add multifields (e.g., used for completion suggester)
            addMultiFields(context, shape);
        } catch (Exception e) {
            if (ignoreMalformed.value() == false) {
                throw new MapperParsingException("failed to parse field [{}] of type [{}]", e, fieldType().name(),
                    fieldType().typeName());
            }
            context.addIgnoredField(mappedFieldType.name());
        }
    }

    public boolean ignoreMalformed() {
        return ignoreMalformed.value();
    }

    public boolean ignoreZValue() {
        return ignoreZValue.value();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy