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

org.elasticsearch.search.aggregations.support.ValuesSourceParser Maven / Gradle / Ivy

There is a newer version: 8.14.1
Show newest version
/*
 * 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.
 */

package org.elasticsearch.search.aggregations.support;

import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.core.BooleanFieldMapper;
import org.elasticsearch.index.mapper.core.DateFieldMapper;
import org.elasticsearch.index.mapper.core.NumberFieldMapper;
import org.elasticsearch.index.mapper.ip.IpFieldMapper;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.Script.ScriptField;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptParameterParser;
import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.SearchParseException;
import org.elasticsearch.search.aggregations.InternalAggregation;
import org.elasticsearch.search.aggregations.support.format.ValueFormat;
import org.elasticsearch.search.internal.SearchContext;
import org.joda.time.DateTimeZone;

import java.io.IOException;
import java.util.Collections;
import java.util.Map;

import static com.google.common.collect.Maps.newHashMap;

/**
 *
 */
public class ValuesSourceParser {

    static final ParseField TIME_ZONE = new ParseField("time_zone");

    public static Builder any(String aggName, InternalAggregation.Type aggType, SearchContext context) {
        return new Builder<>(aggName, aggType, context, ValuesSource.class);
    }

    public static Builder numeric(String aggName, InternalAggregation.Type aggType, SearchContext context) {
        return new Builder<>(aggName, aggType, context, ValuesSource.Numeric.class).targetValueType(ValueType.NUMERIC);
    }

    public static Builder bytes(String aggName, InternalAggregation.Type aggType, SearchContext context) {
        return new Builder<>(aggName, aggType, context, ValuesSource.Bytes.class).targetValueType(ValueType.STRING);
    }

    public static Builder geoPoint(String aggName, InternalAggregation.Type aggType, SearchContext context) {
        return new Builder<>(aggName, aggType, context, ValuesSource.GeoPoint.class).targetValueType(ValueType.GEOPOINT).scriptable(false);
    }

    public static class Input {
        private String field = null;
        private Script script = null;
        @Deprecated
        private Map params = null; // TODO Remove in 3.0
        private ValueType valueType = null;
        private String format = null;
        private Object missing = null;
        private DateTimeZone timezone = DateTimeZone.UTC;

        public DateTimeZone timezone() {
            return this.timezone;
        }
    }

    private final String aggName;
    private final InternalAggregation.Type aggType;
    private final SearchContext context;
    private final Class valuesSourceType;

    private boolean scriptable = true;
    private boolean formattable = false;
    private boolean timezoneAware = false;
    private ValueType targetValueType = null;
    private ScriptParameterParser scriptParameterParser = new ScriptParameterParser();

    private Input input = new Input();

    private ValuesSourceParser(String aggName, InternalAggregation.Type aggType, SearchContext context, Class valuesSourceType) {
        this.aggName = aggName;
        this.aggType = aggType;
        this.context = context;
        this.valuesSourceType = valuesSourceType;
    }

    public boolean token(String currentFieldName, XContentParser.Token token, XContentParser parser) throws IOException {
        if ("missing".equals(currentFieldName) && token.isValue()) {
            input.missing = parser.objectText();
            return true;
        }
        if (token == XContentParser.Token.VALUE_STRING) {
            if ("field".equals(currentFieldName)) {
                input.field = parser.text();
            } else if (formattable && "format".equals(currentFieldName)) {
                input.format = parser.text();
            } else if (timezoneAware && context.parseFieldMatcher().match(currentFieldName, TIME_ZONE)) {
                input.timezone = DateTimeZone.forID(parser.text());
            } else if (scriptable) {
                if ("value_type".equals(currentFieldName) || "valueType".equals(currentFieldName)) {
                    input.valueType = ValueType.resolveForScript(parser.text());
                    if (targetValueType != null && input.valueType.isNotA(targetValueType)) {
                        throw new SearchParseException(context, aggType.name() + " aggregation [" + aggName +
                                "] was configured with an incompatible value type [" + input.valueType + "]. [" + aggType +
                                "] aggregation can only work on value of type [" + targetValueType + "]",
                                parser.getTokenLocation());
                    }
                } else if (!scriptParameterParser.token(currentFieldName, token, parser, context.parseFieldMatcher())) {
                    return false;
                }
                return true;
            } else {
                return false;
            }
            return true;
        }
        if (token == XContentParser.Token.VALUE_NUMBER) {
            if (timezoneAware && context.parseFieldMatcher().match(currentFieldName, TIME_ZONE)) {
                input.timezone = DateTimeZone.forOffsetHours(parser.intValue());
            } else {
                return false;
            }
            return true;
        }
        if (scriptable && token == XContentParser.Token.START_OBJECT) {
            if (context.parseFieldMatcher().match(currentFieldName, ScriptField.SCRIPT)) {
                input.script = Script.parse(parser, context.parseFieldMatcher());
                return true;
            } else if ("params".equals(currentFieldName)) {
                input.params = parser.map();
                return true;
            }
            return false;
        }

        return false;
    }

    public ValuesSourceConfig config() {

        if (input.script == null) { // Didn't find anything using the new API so try using the old one instead
            ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue();
            if (scriptValue != null) {
                if (input.params == null) {
                    input.params = newHashMap();
                }
                input.script = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), input.params);
            }
        }

        ValueType valueType = input.valueType != null ? input.valueType : targetValueType;

        if (input.field == null) {
            if (input.script == null) {
                ValuesSourceConfig config = new ValuesSourceConfig(ValuesSource.class);
                config.format = resolveFormat(null, valueType);
                return config;
            }
            Class valuesSourceType = valueType != null ? (Class) valueType.getValuesSourceType() : this.valuesSourceType;
            if (valuesSourceType == null || valuesSourceType == ValuesSource.class) {
                // the specific value source type is undefined, but for scripts, we need to have a specific value source
                // type to know how to handle the script values, so we fallback on Bytes
                valuesSourceType = ValuesSource.Bytes.class;
            }
            ValuesSourceConfig config = new ValuesSourceConfig(valuesSourceType);
            config.missing = input.missing;
            config.format = resolveFormat(input.format, valueType);
            config.script = createScript();
            config.scriptValueType = valueType;
            return config;
        }

        MappedFieldType fieldType = context.smartNameFieldTypeFromAnyType(input.field);
        if (fieldType == null) {
            Class valuesSourceType = valueType != null ? (Class) valueType.getValuesSourceType() : this.valuesSourceType;
            ValuesSourceConfig config = new ValuesSourceConfig<>(valuesSourceType);
            config.missing = input.missing;
            config.format = resolveFormat(input.format, valueType);
            config.unmapped = true;
            if (valueType != null) {
                // todo do we really need this for unmapped?
                config.scriptValueType = valueType;
            }
            return config;
        }

        IndexFieldData indexFieldData = context.fieldData().getForField(fieldType);

        ValuesSourceConfig config;
        if (valuesSourceType == ValuesSource.class) {
            if (indexFieldData instanceof IndexNumericFieldData) {
                config = new ValuesSourceConfig<>(ValuesSource.Numeric.class);
            } else if (indexFieldData instanceof IndexGeoPointFieldData) {
                config = new ValuesSourceConfig<>(ValuesSource.GeoPoint.class);
            } else {
                config = new ValuesSourceConfig<>(ValuesSource.Bytes.class);
            }
        } else {
            config = new ValuesSourceConfig(valuesSourceType);
        }

        config.fieldContext = new FieldContext(input.field, indexFieldData, fieldType);
        config.missing = input.missing;
        config.script = createScript();
        config.format = resolveFormat(input.format, input.timezone, fieldType);
        return config;
    }

    private SearchScript createScript() {
        return input.script == null ? null : context.scriptService().search(context.lookup(), input.script, ScriptContext.Standard.AGGS, Collections.emptyMap());
    }

    private static ValueFormat resolveFormat(@Nullable String format, @Nullable ValueType valueType) {
        if (valueType == null) {
            return ValueFormat.RAW; // we can't figure it out
        }
        ValueFormat valueFormat = valueType.defaultFormat;
        if (valueFormat != null && valueFormat instanceof ValueFormat.Patternable && format != null) {
            return ((ValueFormat.Patternable) valueFormat).create(format);
        }
        return valueFormat;
    }

    private static ValueFormat resolveFormat(@Nullable String format, @Nullable DateTimeZone timezone,  MappedFieldType fieldType) {
        if (fieldType instanceof  DateFieldMapper.DateFieldType) {
            return format != null ? ValueFormat.DateTime.format(format, timezone) : ValueFormat.DateTime.mapper((DateFieldMapper.DateFieldType) fieldType, timezone);
        }
        if (fieldType instanceof IpFieldMapper.IpFieldType) {
            return ValueFormat.IPv4;
        }
        if (fieldType instanceof BooleanFieldMapper.BooleanFieldType) {
            return ValueFormat.BOOLEAN;
        }
        if (fieldType instanceof NumberFieldMapper.NumberFieldType) {
            return format != null ? ValueFormat.Number.format(format) : ValueFormat.RAW;
        }
        return ValueFormat.RAW;
    }

    public Input input() {
        return this.input;
    }

    public static class Builder {

        private final ValuesSourceParser parser;

        private Builder(String aggName, InternalAggregation.Type aggType, SearchContext context, Class valuesSourceType) {
            parser = new ValuesSourceParser<>(aggName, aggType, context, valuesSourceType);
        }

        public Builder scriptable(boolean scriptable) {
            parser.scriptable = scriptable;
            return this;
        }

        public Builder formattable(boolean formattable) {
            parser.formattable = formattable;
            return this;
        }

        public Builder timezoneAware(boolean timezoneAware) {
            parser.timezoneAware = timezoneAware;
            return this;
        }

        public Builder targetValueType(ValueType valueType) {
            parser.targetValueType = valueType;
            return this;
        }

        public ValuesSourceParser build() {
            return parser;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy