blob: 75b3ad9743e3d1b42b4cd81235e812e90e8d9ad1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file inputtributed with
* this work for additional information regarding copyright ownership.
* The ASF 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
* inputtributed under the License is inputtributed 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.apache.solr.analytics;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import org.apache.solr.analytics.facet.AnalyticsFacet;
import org.apache.solr.analytics.facet.PivotFacet;
import org.apache.solr.analytics.facet.AbstractSolrQueryFacet;
import org.apache.solr.analytics.facet.QueryFacet;
import org.apache.solr.analytics.facet.RangeFacet;
import org.apache.solr.analytics.facet.StreamingFacet;
import org.apache.solr.analytics.facet.ValueFacet;
import org.apache.solr.analytics.facet.AbstractSolrQueryFacet.FacetValueQueryExecuter;
import org.apache.solr.analytics.function.ExpressionCalculator;
import org.apache.solr.analytics.function.ReductionCollectionManager;
import org.apache.solr.analytics.util.AnalyticsResponseHeadings;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.Filter;
/**
* The manager for faceted analytics. This class manages one grouping of facets and expressions to compute
* over those facets.
*
* <p>
* This class will only manage generating faceted results, not overall results.
*/
public class AnalyticsGroupingManager {
private final String name;
private final ReductionCollectionManager reductionCollectionManager;
private final Collection<AnalyticsExpression> topLevelExpressions;
private final ExpressionCalculator expressionCalculator;
private final Map<String, AnalyticsFacet> facets;
public AnalyticsGroupingManager(String name,
ReductionCollectionManager reductionCollectionManager,
Collection<AnalyticsExpression> topLevelExpressions) {
this.name = name;
this.reductionCollectionManager = reductionCollectionManager;
this.topLevelExpressions = topLevelExpressions;
this.expressionCalculator = new ExpressionCalculator(topLevelExpressions);
this.facets = new HashMap<>();
}
// This is outside of the method, since it is used in the lambda and cannot be a local non-final variable
private boolean hasStreamingFacets;
/**
* Get the {@link StreamingFacet}s (e.g. {@link ValueFacet} and {@link PivotFacet}) contained within this grouping,
* returning them through the given consumer.
*
* @param cons where the streaming facets are passed to
* @return whether the grouping contains streaming facets
*/
public boolean getStreamingFacets(Consumer<StreamingFacet> cons) {
hasStreamingFacets = false;
facets.forEach( (name, facet) -> {
if (facet instanceof StreamingFacet) {
cons.accept((StreamingFacet)facet);
hasStreamingFacets = true;
}
});
return hasStreamingFacets;
}
/**
* Create the {@link FacetValueQueryExecuter}s for all {@link AbstractSolrQueryFacet}s
* (e.g. {@link QueryFacet} and {@link RangeFacet}) contained within this grouping.
* The executers are returned through the given consumer.
*
* <p>
* One {@link FacetValueQueryExecuter} is created for each facet value to be returned for a facet.
* Since every {@link AbstractSolrQueryFacet} has discrete and user-defined facet values,
* unlike {@link StreamingFacet}s, a discrete number of {@link FacetValueQueryExecuter}s are created and returned.
*
* @param filter representing the overall Solr Query of the request,
* will be combined with the facet value queries
* @param queryRequest from the overall search request
* @param cons where the executers are passed to
*/
public void getFacetExecuters(Filter filter, SolrQueryRequest queryRequest, Consumer<FacetValueQueryExecuter> cons) {
facets.forEach( (name, facet) -> {
if (facet instanceof AbstractSolrQueryFacet) {
((AbstractSolrQueryFacet)facet).createFacetValueExecuters(filter, queryRequest, cons);
}
});
}
/**
* Add a facet to the grouping. All expressions in this grouping will be computed over the facet.
*
* @param facet to compute expressions over
*/
public void addFacet(AnalyticsFacet facet) {
facet.setExpressionCalculator(expressionCalculator);
facet.setReductionCollectionManager(reductionCollectionManager);
facets.put(facet.getName(), facet);
}
/**
* Import the shard data for this grouping from a bit-stream,
* exported by the {@link #exportShardData} method in the each of the collection's shards.
*
* @param input The bit-stream to import the grouping data from
* @throws IOException if an exception occurs while reading from the {@link DataInput}
*/
public void importShardData(DataInput input) throws IOException {
// This allows mergeData() to import from the same input everytime it is called
// while the facets are importing.
reductionCollectionManager.setShardInput(input);
int sz = input.readInt();
for (int i = 0; i < sz; ++i) {
facets.get(input.readUTF()).importShardData(input);
}
}
/**
* Export the shard data for this grouping through a bit-stream,
* to be imported by the {@link #importShardData} method in the originating shard.
*
* @param output The bit-stream to output the grouping data through
* @throws IOException if an exception occurs while writing to the {@link DataOutput}
*/
public void exportShardData(DataOutput output) throws IOException {
// This allows exportData() to export to the same output everytime it is called
// while the facets are exporting.
reductionCollectionManager.setShardOutput(output);
output.writeInt(facets.size());
for (Entry<String,AnalyticsFacet> facet : facets.entrySet()) {
output.writeUTF(facet.getKey());
facet.getValue().exportShardData(output);
}
}
/**
* Get the {@link ReductionCollectionManager} that manages the collection of reduction data for the expressions
* contained within this grouping.
*
* @return the grouping's reduction manager
*/
public ReductionCollectionManager getReductionManager() {
return reductionCollectionManager;
}
/**
* Create the response for this grouping, a mapping from each of it's facets' names to the facet's response.
*
* @return the named list representation of the response
*/
public Map<String,Object> createResponse() {
Map<String,Object> response = new HashMap<>();
// Add the value facet buckets to the output
facets.forEach( (name, facet) -> response.put(name, facet.createResponse()) );
return response;
}
/**
* Create the response for this grouping, but in the old style of response.
* This response has a bucket for the following if they are contained in the grouping:
* FieldFacets, RangeFacets and QueryFacets.
* Each facet's name and response are put into the bucket corresponding to its type.
* <p>
* Since groupings in the old notation must also return overall results, the overall results are
* passed in and the values are used to populate the grouping response.
*
* @param overallResults of the expressions to add to the grouping response
* @return the named list representation of the response
*/
public NamedList<Object> createOldResponse(Map<String,Object> overallResults) {
NamedList<Object> response = new NamedList<>();
topLevelExpressions.forEach( expression -> response.add(expression.getName(), overallResults.get(name + expression.getName())));
NamedList<Object> fieldFacetResults = new NamedList<>();
NamedList<Object> rangeFacetResults = new NamedList<>();
NamedList<Object> queryFacetResults = new NamedList<>();
// Add the field facet buckets to the output
facets.forEach( (name, facet) -> {
// The old style of request only accepts field facets
// So we can assume that all value facets are field facets
if (facet instanceof ValueFacet) {
fieldFacetResults.add(name, facet.createOldResponse());
} else if (facet instanceof RangeFacet) {
rangeFacetResults.add(name, facet.createOldResponse());
} else if (facet instanceof QueryFacet) {
queryFacetResults.add(name, facet.createOldResponse());
}
});
if (fieldFacetResults.size() > 0) {
response.add(AnalyticsResponseHeadings.FIELD_FACETS, fieldFacetResults);
}
if (rangeFacetResults.size() > 0) {
response.add(AnalyticsResponseHeadings.RANGE_FACETS, rangeFacetResults);
}
if (queryFacetResults.size() > 0) {
response.add(AnalyticsResponseHeadings.QUERY_FACETS, queryFacetResults);
}
return response;
}
/**
* Get the name of the grouping.
*
* @return the grouping name
*/
public String getName() {
return name;
}
}