/*
* 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.apache.lucene.util.BytesRef;
import org.elasticsearch.cache.recycler.PageCacheRecycler;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.fielddata.IndexOrdinalsFieldData;
import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData;
import org.elasticsearch.index.mapper.core.DateFieldMapper;
import org.elasticsearch.search.SearchParseException;
import org.elasticsearch.search.aggregations.AggregationExecutionException;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
/**
*
*/
public class AggregationContext {
private final SearchContext searchContext;
public AggregationContext(SearchContext searchContext) {
this.searchContext = searchContext;
}
public SearchContext searchContext() {
return searchContext;
}
public PageCacheRecycler pageCacheRecycler() {
return searchContext.pageCacheRecycler();
}
public BigArrays bigArrays() {
return searchContext.bigArrays();
}
/** Get a value source given its configuration. A return value of null indicates that
* no value source could be built. */
@Nullable
public <VS extends ValuesSource> VS valuesSource(ValuesSourceConfig<VS> config, SearchContext context) throws IOException {
assert config.valid() : "value source config is invalid - must have either a field context or a script or marked as unmapped";
final VS vs;
if (config.unmapped) {
if (config.missing == null) {
// otherwise we will have values because of the missing value
vs = null;
} else if (ValuesSource.Numeric.class.isAssignableFrom(config.valueSourceType)) {
vs = (VS) ValuesSource.Numeric.EMPTY;
} else if (ValuesSource.GeoPoint.class.isAssignableFrom(config.valueSourceType)) {
vs = (VS) ValuesSource.GeoPoint.EMPTY;
} else if (ValuesSource.class.isAssignableFrom(config.valueSourceType)
|| ValuesSource.Bytes.class.isAssignableFrom(config.valueSourceType)
|| ValuesSource.Bytes.WithOrdinals.class.isAssignableFrom(config.valueSourceType)) {
vs = (VS) ValuesSource.Bytes.EMPTY;
} else {
throw new SearchParseException(searchContext, "Can't deal with unmapped ValuesSource type " + config.valueSourceType, null);
}
} else {
vs = originalValuesSource(config);
}
if (config.missing == null) {
return vs;
}
if (vs instanceof ValuesSource.Bytes) {
final BytesRef missing = new BytesRef(config.missing.toString());
if (vs instanceof ValuesSource.Bytes.WithOrdinals) {
return (VS) MissingValues.replaceMissing((ValuesSource.Bytes.WithOrdinals) vs, missing);
} else {
return (VS) MissingValues.replaceMissing((ValuesSource.Bytes) vs, missing);
}
} else if (vs instanceof ValuesSource.Numeric) {
Number missing = null;
if (config.missing instanceof Number) {
missing = (Number) config.missing;
} else {
if (config.fieldContext != null && config.fieldContext.fieldType() instanceof DateFieldMapper.DateFieldType) {
final DateFieldMapper.DateFieldType fieldType = (DateFieldMapper.DateFieldType) config.fieldContext.fieldType();
try {
missing = fieldType.dateTimeFormatter().parser().parseDateTime(config.missing.toString()).getMillis();
} catch (IllegalArgumentException e) {
throw new SearchParseException(context, "Expected a date value in [missing] but got [" + config.missing + "]", null, e);
}
} else {
try {
missing = Double.parseDouble(config.missing.toString());
} catch (NumberFormatException e) {
throw new SearchParseException(context, "Expected a numeric value in [missing] but got [" + config.missing + "]", null, e);
}
}
}
return (VS) MissingValues.replaceMissing((ValuesSource.Numeric) vs, missing);
} else if (vs instanceof ValuesSource.GeoPoint) {
// TODO: also support the structured formats of geo points
final GeoPoint missing = GeoUtils.parseGeoPoint(config.missing.toString(), new GeoPoint());
return (VS) MissingValues.replaceMissing((ValuesSource.GeoPoint) vs, missing);
} else {
// Should not happen
throw new SearchParseException(searchContext, "Can't apply missing values on a " + vs.getClass(), null);
}
}
/**
* Return the original values source, before we apply `missing`.
*/
private <VS extends ValuesSource> VS originalValuesSource(ValuesSourceConfig<VS> config) throws IOException {
if (config.fieldContext == null) {
if (ValuesSource.Numeric.class.isAssignableFrom(config.valueSourceType)) {
return (VS) numericScript(config);
}
if (ValuesSource.Bytes.class.isAssignableFrom(config.valueSourceType)) {
return (VS) bytesScript(config);
}
throw new AggregationExecutionException("value source of type [" + config.valueSourceType.getSimpleName() + "] is not supported by scripts");
}
if (ValuesSource.Numeric.class.isAssignableFrom(config.valueSourceType)) {
return (VS) numericField(config);
}
if (ValuesSource.GeoPoint.class.isAssignableFrom(config.valueSourceType)) {
return (VS) geoPointField(config);
}
// falling back to bytes values
return (VS) bytesField(config);
}
private ValuesSource.Numeric numericScript(ValuesSourceConfig<?> config) throws IOException {
return new ValuesSource.Numeric.Script(config.script, config.scriptValueType);
}
private ValuesSource.Numeric numericField(ValuesSourceConfig<?> config) throws IOException {
if (!(config.fieldContext.indexFieldData() instanceof IndexNumericFieldData)) {
throw new IllegalArgumentException("Expected numeric type on field [" + config.fieldContext.field() +
"], but got [" + config.fieldContext.fieldType().typeName() + "]");
}
ValuesSource.Numeric dataSource = new ValuesSource.Numeric.FieldData((IndexNumericFieldData) config.fieldContext.indexFieldData());
if (config.script != null) {
dataSource = new ValuesSource.Numeric.WithScript(dataSource, config.script);
}
return dataSource;
}
private ValuesSource bytesField(ValuesSourceConfig<?> config) throws IOException {
final IndexFieldData<?> indexFieldData = config.fieldContext.indexFieldData();
ValuesSource dataSource;
if (indexFieldData instanceof ParentChildIndexFieldData) {
dataSource = new ValuesSource.Bytes.WithOrdinals.ParentChild((ParentChildIndexFieldData) indexFieldData);
} else if (indexFieldData instanceof IndexOrdinalsFieldData) {
dataSource = new ValuesSource.Bytes.WithOrdinals.FieldData((IndexOrdinalsFieldData) indexFieldData);
} else {
dataSource = new ValuesSource.Bytes.FieldData(indexFieldData);
}
if (config.script != null) {
dataSource = new ValuesSource.WithScript(dataSource, config.script);
}
return dataSource;
}
private ValuesSource.Bytes bytesScript(ValuesSourceConfig<?> config) throws IOException {
return new ValuesSource.Bytes.Script(config.script);
}
private ValuesSource.GeoPoint geoPointField(ValuesSourceConfig<?> config) throws IOException {
if (!(config.fieldContext.indexFieldData() instanceof IndexGeoPointFieldData)) {
throw new IllegalArgumentException("Expected geo_point type on field [" + config.fieldContext.field() +
"], but got [" + config.fieldContext.fieldType().typeName() + "]");
}
return new ValuesSource.GeoPoint.Fielddata((IndexGeoPointFieldData) config.fieldContext.indexFieldData());
}
}