diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/ParsedFilters.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/ParsedFilters.java new file mode 100644 index 00000000000..a77577a3ccc --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/filters/ParsedFilters.java @@ -0,0 +1,141 @@ +/* + * 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.bucket.filters; + +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParserUtils; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.Aggregations; +import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; + +public class ParsedFilters extends ParsedMultiBucketAggregation implements Filters { + + private Map bucketMap; + + @Override + public String getType() { + return FiltersAggregationBuilder.NAME; + } + + @Override + public List getBuckets() { + return buckets; + } + + @Override + public ParsedBucket getBucketByKey(String key) { + if (bucketMap == null) { + bucketMap = new HashMap<>(buckets.size()); + for (ParsedBucket bucket : buckets) { + bucketMap.put(bucket.getKey(), bucket); + } + } + return bucketMap.get(key); + } + + private static ObjectParser PARSER = + new ObjectParser<>(ParsedFilters.class.getSimpleName(), true, ParsedFilters::new); + static { + declareMultiBucketAggregationFields(PARSER, + parser -> ParsedBucket.fromXContent(parser, false), + parser -> ParsedBucket.fromXContent(parser, true)); + } + + public static ParsedFilters fromXContent(XContentParser parser, String name) throws IOException { + ParsedFilters aggregation = PARSER.parse(parser, null); + aggregation.setName(name); + // in case this is not a keyed aggregation, we need to add numeric keys to the buckets + if (aggregation.keyed == false) { + int i = 0; + for (ParsedBucket bucket : aggregation.buckets) { + assert bucket.key == null; + bucket.key = String.valueOf(i); + i++; + } + } + return aggregation; + } + + public static class ParsedBucket extends ParsedMultiBucketAggregation.ParsedBucket implements Filters.Bucket { + + private String key; + + @Override + public String getKey() { + return key; + } + + @Override + public String getKeyAsString() { + return key; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + if (isKeyed()) { + builder.startObject(key); + } else { + builder.startObject(); + } + builder.field(CommonFields.DOC_COUNT.getPreferredName(), getDocCount()); + getAggregations().toXContentInternal(builder, params); + builder.endObject(); + return builder; + } + + + static ParsedBucket fromXContent(XContentParser parser, boolean keyed) throws IOException { + final ParsedBucket bucket = new ParsedBucket(); + bucket.setKeyed(keyed); + XContentParser.Token token = parser.currentToken(); + String currentFieldName = parser.currentName(); + if (keyed) { + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + bucket.key = currentFieldName; + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + } + + List aggregations = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token.isValue()) { + if (CommonFields.DOC_COUNT.getPreferredName().equals(currentFieldName)) { + bucket.setDocCount(parser.longValue()); + } + } else if (token == XContentParser.Token.START_OBJECT) { + aggregations.add(XContentParserUtils.parseTypedKeysObject(parser, Aggregation.TYPED_KEYS_DELIMITER, Aggregation.class)); + } + } + bucket.setAggregations(new Aggregations(aggregations)); + return bucket; + } + } +} diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java index 48fa42ad3ef..871cf2756f2 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/AggregationsTests.java @@ -27,6 +27,7 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.rest.action.search.RestSearchAction; import org.elasticsearch.search.aggregations.bucket.filter.InternalFilterTests; +import org.elasticsearch.search.aggregations.bucket.filters.InternalFiltersTests; import org.elasticsearch.search.aggregations.bucket.geogrid.InternalGeoHashGridTests; import org.elasticsearch.search.aggregations.bucket.global.InternalGlobalTests; import org.elasticsearch.search.aggregations.bucket.histogram.InternalDateHistogramTests; @@ -122,6 +123,7 @@ public class AggregationsTests extends ESTestCase { aggsTests.add(new InternalRangeTests()); aggsTests.add(new InternalDateRangeTests()); aggsTests.add(new InternalGeoDistanceTests()); + aggsTests.add(new InternalFiltersTests()); return Collections.unmodifiableList(aggsTests); } diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/filters/InternalFiltersTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/filters/InternalFiltersTests.java index b21f201e79b..03b5cb13d9c 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/bucket/filters/InternalFiltersTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/bucket/filters/InternalFiltersTests.java @@ -21,8 +21,9 @@ package org.elasticsearch.search.aggregations.bucket.filters; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.search.aggregations.InternalAggregations; +import org.elasticsearch.search.aggregations.InternalMultiBucketAggregationTestCase; +import org.elasticsearch.search.aggregations.ParsedMultiBucketAggregation; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; -import org.elasticsearch.test.InternalAggregationTestCase; import org.junit.Before; import java.util.ArrayList; @@ -30,30 +31,36 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -public class InternalFiltersTests extends InternalAggregationTestCase { +public class InternalFiltersTests extends InternalMultiBucketAggregationTestCase { private boolean keyed; - private final List keys = new ArrayList<>(); + private List keys; @Override @Before public void setUp() throws Exception { super.setUp(); keyed = randomBoolean(); - int numKeys = randomIntBetween(1,10); - for (int i = 0; i < numKeys; i++) { - keys.add(randomAlphaOfLength(5)); + keys = new ArrayList<>(); + int numBuckets = randomIntBetween(1, 5); + for (int i = 0; i < numBuckets; i++) { + if (keyed) { + keys.add(randomAlphaOfLength(5)); + } else { + // this is what the FiltersAggregationBuilder ctor does when not providing KeyedFilter + keys.add(String.valueOf(i)); + } } - } @Override - protected InternalFilters createTestInstance(String name, List pipelineAggregators, Map metaData) { + protected InternalFilters createTestInstance(String name, List pipelineAggregators, Map metaData, + InternalAggregations aggregations) { final List buckets = new ArrayList<>(); for (int i = 0; i < keys.size(); ++i) { String key = keys.get(i); int docCount = randomIntBetween(0, 1000); - buckets.add( new InternalFilters.InternalBucket(key, docCount, InternalAggregations.EMPTY, keyed)); + buckets.add(new InternalFilters.InternalBucket(key, docCount, aggregations, keyed)); } return new InternalFilters(name, buckets, keyed, pipelineAggregators, metaData); } @@ -80,4 +87,9 @@ public class InternalFiltersTests extends InternalAggregationTestCase implementationClass() { + return ParsedFilters.class; + } + } diff --git a/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java index bd084bd5c08..7bd02f82c5e 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/InternalAggregationTestCase.java @@ -40,6 +40,8 @@ import org.elasticsearch.search.aggregations.InternalAggregation; import org.elasticsearch.search.aggregations.ParsedAggregation; import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter; +import org.elasticsearch.search.aggregations.bucket.filters.FiltersAggregationBuilder; +import org.elasticsearch.search.aggregations.bucket.filters.ParsedFilters; import org.elasticsearch.search.aggregations.bucket.geogrid.GeoGridAggregationBuilder; import org.elasticsearch.search.aggregations.bucket.geogrid.ParsedGeoHashGrid; import org.elasticsearch.search.aggregations.bucket.global.GlobalAggregationBuilder; @@ -170,6 +172,7 @@ public abstract class InternalAggregationTestCase map.put(RangeAggregationBuilder.NAME, (p, c) -> ParsedRange.fromXContent(p, (String) c)); map.put(DateRangeAggregationBuilder.NAME, (p, c) -> ParsedDateRange.fromXContent(p, (String) c)); map.put(GeoDistanceAggregationBuilder.NAME, (p, c) -> ParsedGeoDistance.fromXContent(p, (String) c)); + map.put(FiltersAggregationBuilder.NAME, (p, c) -> ParsedFilters.fromXContent(p, (String) c)); namedXContents = map.entrySet().stream() .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue()))