/* * 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 com.google.common.collect.ImmutableMap; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.search.SearchParseException; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregatorFactory; import org.elasticsearch.search.internal.SearchContext; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A registry for all the aggregator parser, also servers as the main parser for the aggregations module */ public class AggregatorParsers { public static final Pattern VALID_AGG_NAME = Pattern.compile("[^\\[\\]>]+"); private final ImmutableMap<String, Aggregator.Parser> aggParsers; private final ImmutableMap<String, PipelineAggregator.Parser> pipelineAggregatorParsers; /** * Constructs the AggregatorParsers out of all the given parsers * * @param aggParsers * The available aggregator parsers (dynamically injected by the * {@link org.elasticsearch.search.SearchModule} * ). */ @Inject public AggregatorParsers(Set<Aggregator.Parser> aggParsers, Set<PipelineAggregator.Parser> pipelineAggregatorParsers) { MapBuilder<String, Aggregator.Parser> aggParsersBuilder = MapBuilder.newMapBuilder(); for (Aggregator.Parser parser : aggParsers) { aggParsersBuilder.put(parser.type(), parser); } this.aggParsers = aggParsersBuilder.immutableMap(); MapBuilder<String, PipelineAggregator.Parser> pipelineAggregatorParsersBuilder = MapBuilder.newMapBuilder(); for (PipelineAggregator.Parser parser : pipelineAggregatorParsers) { pipelineAggregatorParsersBuilder.put(parser.type(), parser); } this.pipelineAggregatorParsers = pipelineAggregatorParsersBuilder.immutableMap(); } /** * Returns the parser that is registered under the given aggregation type. * * @param type The aggregation type * @return The parser associated with the given aggregation type. */ public Aggregator.Parser parser(String type) { return aggParsers.get(type); } /** * Returns the parser that is registered under the given pipeline aggregator * type. * * @param type * The pipeline aggregator type * @return The parser associated with the given pipeline aggregator type. */ public PipelineAggregator.Parser pipelineAggregator(String type) { return pipelineAggregatorParsers.get(type); } /** * Parses the aggregation request recursively generating aggregator factories in turn. * * @param parser The input xcontent that will be parsed. * @param context The search context. * * @return The parsed aggregator factories. * * @throws IOException When parsing fails for unknown reasons. */ public AggregatorFactories parseAggregators(XContentParser parser, SearchContext context) throws IOException { return parseAggregators(parser, context, 0); } private AggregatorFactories parseAggregators(XContentParser parser, SearchContext context, int level) throws IOException { Matcher validAggMatcher = VALID_AGG_NAME.matcher(""); AggregatorFactories.Builder factories = new AggregatorFactories.Builder(); XContentParser.Token token = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token != XContentParser.Token.FIELD_NAME) { throw new SearchParseException(context, "Unexpected token " + token + " in [aggs]: aggregations definitions must start with the name of the aggregation.", parser.getTokenLocation()); } final String aggregationName = parser.currentName(); if (!validAggMatcher.reset(aggregationName).matches()) { throw new SearchParseException(context, "Invalid aggregation name [" + aggregationName + "]. Aggregation names must be alpha-numeric and can only contain '_' and '-'", parser.getTokenLocation()); } token = parser.nextToken(); if (token != XContentParser.Token.START_OBJECT) { throw new SearchParseException(context, "Aggregation definition for [" + aggregationName + " starts with a [" + token + "], expected a [" + XContentParser.Token.START_OBJECT + "].", parser.getTokenLocation()); } AggregatorFactory aggFactory = null; PipelineAggregatorFactory pipelineAggregatorFactory = null; AggregatorFactories subFactories = null; Map<String, Object> metaData = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token != XContentParser.Token.FIELD_NAME) { throw new SearchParseException(context, "Expected [" + XContentParser.Token.FIELD_NAME + "] under a [" + XContentParser.Token.START_OBJECT + "], but got a [" + token + "] in [" + aggregationName + "]", parser.getTokenLocation()); } final String fieldName = parser.currentName(); token = parser.nextToken(); if ("aggregations_binary".equals(fieldName)) { if (subFactories != null) { throw new SearchParseException(context, "Found two sub aggregation definitions under [" + aggregationName + "]", parser.getTokenLocation()); } XContentParser binaryParser = null; if (token == XContentParser.Token.VALUE_STRING || token == XContentParser.Token.VALUE_EMBEDDED_OBJECT) { byte[] source = parser.binaryValue(); binaryParser = XContentFactory.xContent(source).createParser(source); } else { throw new SearchParseException(context, "Expected [" + XContentParser.Token.VALUE_STRING + " or " + XContentParser.Token.VALUE_EMBEDDED_OBJECT + "] for [" + fieldName + "], but got a [" + token + "] in [" + aggregationName + "]", parser.getTokenLocation()); } XContentParser.Token binaryToken = binaryParser.nextToken(); if (binaryToken != XContentParser.Token.START_OBJECT) { throw new SearchParseException(context, "Expected [" + XContentParser.Token.START_OBJECT + "] as first token when parsing [" + fieldName + "], but got a [" + binaryToken + "] in [" + aggregationName + "]", parser.getTokenLocation()); } subFactories = parseAggregators(binaryParser, context, level + 1); } else if (token == XContentParser.Token.START_OBJECT) { switch (fieldName) { case "meta": metaData = parser.map(); break; case "aggregations": case "aggs": if (subFactories != null) { throw new SearchParseException(context, "Found two sub aggregation definitions under [" + aggregationName + "]", parser.getTokenLocation()); } subFactories = parseAggregators(parser, context, level + 1); break; default: if (aggFactory != null) { throw new SearchParseException(context, "Found two aggregation type definitions in [" + aggregationName + "]: [" + aggFactory.type + "] and [" + fieldName + "]", parser.getTokenLocation()); } if (pipelineAggregatorFactory != null) { throw new SearchParseException(context, "Found two aggregation type definitions in [" + aggregationName + "]: [" + pipelineAggregatorFactory + "] and [" + fieldName + "]", parser.getTokenLocation()); } Aggregator.Parser aggregatorParser = parser(fieldName); if (aggregatorParser == null) { PipelineAggregator.Parser pipelineAggregatorParser = pipelineAggregator(fieldName); if (pipelineAggregatorParser == null) { throw new SearchParseException(context, "Could not find aggregator type [" + fieldName + "] in [" + aggregationName + "]", parser.getTokenLocation()); } else { pipelineAggregatorFactory = pipelineAggregatorParser.parse(aggregationName, parser, context); } } else { aggFactory = aggregatorParser.parse(aggregationName, parser, context); } } } else { throw new SearchParseException(context, "Expected [" + XContentParser.Token.START_OBJECT + "] under [" + fieldName + "], but got a [" + token + "] in [" + aggregationName + "]", parser.getTokenLocation()); } } if (aggFactory == null && pipelineAggregatorFactory == null) { throw new SearchParseException(context, "Missing definition for aggregation [" + aggregationName + "]", parser.getTokenLocation()); } else if (aggFactory != null) { assert pipelineAggregatorFactory == null; if (metaData != null) { aggFactory.setMetaData(metaData); } if (subFactories != null) { aggFactory.subFactories(subFactories); } if (level == 0) { aggFactory.validate(); } factories.addAggregator(aggFactory); } else { assert pipelineAggregatorFactory != null; if (subFactories != null) { throw new SearchParseException(context, "Aggregation [" + aggregationName + "] cannot define sub-aggregations", parser.getTokenLocation()); } if (level == 0) { pipelineAggregatorFactory .validate(null, factories.getAggregatorFactories(), factories.getPipelineAggregatorFactories()); } if (metaData != null) { pipelineAggregatorFactory.setMetaData(metaData); } factories.addPipelineAggregator(pipelineAggregatorFactory); } } return factories.build(); } }