/* * 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; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregatorFactory; import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.AggregationPath; import org.elasticsearch.search.aggregations.support.AggregationPath.PathElement; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; /** * */ public class AggregatorFactories { public static final AggregatorFactories EMPTY = new Empty(); private AggregatorFactory parent; private AggregatorFactory[] factories; private List<PipelineAggregatorFactory> pipelineAggregatorFactories; public static Builder builder() { return new Builder(); } private AggregatorFactories(AggregatorFactory[] factories, List<PipelineAggregatorFactory> pipelineAggregators) { this.factories = factories; this.pipelineAggregatorFactories = pipelineAggregators; } public List<PipelineAggregator> createPipelineAggregators() throws IOException { List<PipelineAggregator> pipelineAggregators = new ArrayList<>(); for (PipelineAggregatorFactory factory : this.pipelineAggregatorFactories) { pipelineAggregators.add(factory.create()); } return pipelineAggregators; } // hack to avoid token_ranges filtering when aggregating on token range. public boolean hasTokenRangeAggregation() { return factories.length > 0 && (factories[0] instanceof org.elassandra.shard.aggregations.bucket.token.RangeAggregator.Factory); } /** * Create all aggregators so that they can be consumed with multiple * buckets. */ public Aggregator[] createSubAggregators(Aggregator parent) throws IOException { Aggregator[] aggregators = new Aggregator[count()]; for (int i = 0; i < factories.length; ++i) { // TODO: sometimes even sub aggregations always get called with bucket 0, eg. if // you have a terms agg under a top-level filter agg. We should have a way to // propagate the fact that only bucket 0 will be collected with single-bucket // aggs final boolean collectsFromSingleBucket = false; aggregators[i] = factories[i].create(parent.context(), parent, collectsFromSingleBucket); } return aggregators; } public Aggregator[] createTopLevelAggregators(AggregationContext ctx) throws IOException { // These aggregators are going to be used with a single bucket ordinal, no need to wrap the PER_BUCKET ones Aggregator[] aggregators = new Aggregator[factories.length]; for (int i = 0; i < factories.length; i++) { // top-level aggs only get called with bucket 0 final boolean collectsFromSingleBucket = true; aggregators[i] = factories[i].create(ctx, null, collectsFromSingleBucket); } return aggregators; } public int count() { return factories.length; } void setParent(AggregatorFactory parent) { this.parent = parent; for (AggregatorFactory factory : factories) { factory.parent = parent; } } public void validate() { for (AggregatorFactory factory : factories) { factory.validate(); } for (PipelineAggregatorFactory factory : pipelineAggregatorFactories) { factory.validate(parent, factories, pipelineAggregatorFactories); } } private final static class Empty extends AggregatorFactories { private static final AggregatorFactory[] EMPTY_FACTORIES = new AggregatorFactory[0]; private static final Aggregator[] EMPTY_AGGREGATORS = new Aggregator[0]; private static final List<PipelineAggregatorFactory> EMPTY_PIPELINE_AGGREGATORS = new ArrayList<>(); private Empty() { super(EMPTY_FACTORIES, EMPTY_PIPELINE_AGGREGATORS); } @Override public Aggregator[] createSubAggregators(Aggregator parent) { return EMPTY_AGGREGATORS; } @Override public Aggregator[] createTopLevelAggregators(AggregationContext ctx) { return EMPTY_AGGREGATORS; } } public static class Builder { private final Set<String> names = new HashSet<>(); private final List<AggregatorFactory> factories = new ArrayList<>(); private final List<PipelineAggregatorFactory> pipelineAggregatorFactories = new ArrayList<>(); public Builder addAggregator(AggregatorFactory factory) { if (!names.add(factory.name)) { throw new IllegalArgumentException("Two sibling aggregations cannot have the same name: [" + factory.name + "]"); } factories.add(factory); return this; } public Builder addPipelineAggregator(PipelineAggregatorFactory pipelineAggregatorFactory) { this.pipelineAggregatorFactories.add(pipelineAggregatorFactory); return this; } public AggregatorFactories build() { if (factories.isEmpty() && pipelineAggregatorFactories.isEmpty()) { return EMPTY; } List<PipelineAggregatorFactory> orderedpipelineAggregators = resolvePipelineAggregatorOrder(this.pipelineAggregatorFactories, this.factories); return new AggregatorFactories(factories.toArray(new AggregatorFactory[factories.size()]), orderedpipelineAggregators); } private List<PipelineAggregatorFactory> resolvePipelineAggregatorOrder(List<PipelineAggregatorFactory> pipelineAggregatorFactories, List<AggregatorFactory> aggFactories) { Map<String, PipelineAggregatorFactory> pipelineAggregatorFactoriesMap = new HashMap<>(); for (PipelineAggregatorFactory factory : pipelineAggregatorFactories) { pipelineAggregatorFactoriesMap.put(factory.getName(), factory); } Map<String, AggregatorFactory> aggFactoriesMap = new HashMap<>(); for (AggregatorFactory aggFactory : aggFactories) { aggFactoriesMap.put(aggFactory.name, aggFactory); } List<PipelineAggregatorFactory> orderedPipelineAggregatorrs = new LinkedList<>(); List<PipelineAggregatorFactory> unmarkedFactories = new ArrayList<PipelineAggregatorFactory>(pipelineAggregatorFactories); Set<PipelineAggregatorFactory> temporarilyMarked = new HashSet<PipelineAggregatorFactory>(); while (!unmarkedFactories.isEmpty()) { PipelineAggregatorFactory factory = unmarkedFactories.get(0); resolvePipelineAggregatorOrder(aggFactoriesMap, pipelineAggregatorFactoriesMap, orderedPipelineAggregatorrs, unmarkedFactories, temporarilyMarked, factory); } return orderedPipelineAggregatorrs; } private void resolvePipelineAggregatorOrder(Map<String, AggregatorFactory> aggFactoriesMap, Map<String, PipelineAggregatorFactory> pipelineAggregatorFactoriesMap, List<PipelineAggregatorFactory> orderedPipelineAggregators, List<PipelineAggregatorFactory> unmarkedFactories, Set<PipelineAggregatorFactory> temporarilyMarked, PipelineAggregatorFactory factory) { if (temporarilyMarked.contains(factory)) { throw new IllegalArgumentException("Cyclical dependancy found with pipeline aggregator [" + factory.getName() + "]"); } else if (unmarkedFactories.contains(factory)) { temporarilyMarked.add(factory); String[] bucketsPaths = factory.getBucketsPaths(); for (String bucketsPath : bucketsPaths) { List<AggregationPath.PathElement> bucketsPathElements = AggregationPath.parse(bucketsPath).getPathElements(); String firstAggName = bucketsPathElements.get(0).name; if (bucketsPath.equals("_count") || bucketsPath.equals("_key")) { continue; } else if (aggFactoriesMap.containsKey(firstAggName)) { AggregatorFactory aggFactory = aggFactoriesMap.get(firstAggName); for (int i = 1; i < bucketsPathElements.size(); i++) { PathElement pathElement = bucketsPathElements.get(i); String aggName = pathElement.name; if ((i == bucketsPathElements.size() - 1) && (aggName.equalsIgnoreCase("_key") || aggName.equals("_count"))) { break; } else { // Check the non-pipeline sub-aggregator // factories AggregatorFactory[] subFactories = aggFactory.factories.factories; boolean foundSubFactory = false; for (AggregatorFactory subFactory : subFactories) { if (aggName.equals(subFactory.name)) { aggFactory = subFactory; foundSubFactory = true; break; } } // Check the pipeline sub-aggregator factories if (!foundSubFactory && (i == bucketsPathElements.size() - 1)) { List<PipelineAggregatorFactory> subPipelineFactories = aggFactory.factories.pipelineAggregatorFactories; for (PipelineAggregatorFactory subFactory : subPipelineFactories) { if (aggName.equals(subFactory.name())) { foundSubFactory = true; break; } } } if (!foundSubFactory) { throw new IllegalArgumentException("No aggregation [" + aggName + "] found for path [" + bucketsPath + "]"); } } } continue; } else { PipelineAggregatorFactory matchingFactory = pipelineAggregatorFactoriesMap.get(firstAggName); if (matchingFactory != null) { resolvePipelineAggregatorOrder(aggFactoriesMap, pipelineAggregatorFactoriesMap, orderedPipelineAggregators, unmarkedFactories, temporarilyMarked, matchingFactory); } else { throw new IllegalArgumentException("No aggregation found for path [" + bucketsPath + "]"); } } } unmarkedFactories.remove(factory); temporarilyMarked.remove(factory); orderedPipelineAggregators.add(factory); } } AggregatorFactory[] getAggregatorFactories() { return this.factories.toArray(new AggregatorFactory[this.factories.size()]); } List<PipelineAggregatorFactory> getPipelineAggregatorFactories() { return this.pipelineAggregatorFactories; } } }