/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed 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
* 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.apache.solr.analytics.accumulator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import com.google.common.collect.Iterables;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.solr.analytics.accumulator.facet.FacetValueAccumulator;
import org.apache.solr.analytics.accumulator.facet.FieldFacetAccumulator;
import org.apache.solr.analytics.accumulator.facet.QueryFacetAccumulator;
import org.apache.solr.analytics.accumulator.facet.RangeFacetAccumulator;
import org.apache.solr.analytics.expression.Expression;
import org.apache.solr.analytics.expression.ExpressionFactory;
import org.apache.solr.analytics.request.AnalyticsContentHandler;
import org.apache.solr.analytics.request.AnalyticsRequest;
import org.apache.solr.analytics.request.FieldFacetRequest;
import org.apache.solr.analytics.request.FieldFacetRequest.FacetSortSpecification;
import org.apache.solr.analytics.request.QueryFacetRequest;
import org.apache.solr.analytics.request.RangeFacetRequest;
import org.apache.solr.analytics.statistics.StatsCollector;
import org.apache.solr.analytics.util.AnalyticsParams;
import org.apache.solr.analytics.util.RangeEndpointCalculator;
import org.apache.solr.analytics.util.RangeEndpointCalculator.FacetRange;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.DocSet;
import org.apache.solr.search.Filter;
import org.apache.solr.search.QParser;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.SyntaxError;
/**
* A <code>FacetingAccumulator</code> manages the StatsCollectors and Expressions for facets.
*/
public class FacetingAccumulator extends BasicAccumulator implements FacetValueAccumulator {
public static final String MISSING_VALUE = "(MISSING)";
protected boolean basicsAndFieldFacetsComputed;
protected int leafNum;
protected LeafReaderContext leaf;
protected final AnalyticsRequest analyticsRequest;
protected final Map<String,Map<String,Expression[]>> fieldFacetExpressions;
protected final Map<String,Map<String,Expression[]>> rangeFacetExpressions;
protected final Map<String,Map<String,Expression[]>> queryFacetExpressions;
protected final Map<String,Map<String,StatsCollector[]>> fieldFacetCollectors;
protected final Map<String,Map<String,StatsCollector[]>> rangeFacetCollectors;
protected final Map<String,Map<String,StatsCollector[]>> queryFacetCollectors;
protected final List<FieldFacetAccumulator> facetAccumulators;
protected final Set<String> hiddenFieldFacets;
/** the current value of this stat field */
protected final SolrQueryRequest queryRequest;
protected List<RangeFacetRequest> rangeFacets = null;
protected List<QueryFacetRequest> queryFacets = null;
protected long queryCount;
public FacetingAccumulator(SolrIndexSearcher searcher, DocSet docs, AnalyticsRequest request, SolrQueryRequest queryRequest) throws IOException {
// The parent Basic Accumulator keeps track of overall stats while
// the Faceting Accumulator only manages the facet stats
super(searcher, docs, request);
this.analyticsRequest = request;
this.queryRequest = queryRequest;
basicsAndFieldFacetsComputed = false;
List<FieldFacetRequest> fieldFreqs = request.getFieldFacets();
List<RangeFacetRequest> rangeFreqs = request.getRangeFacets();
List<QueryFacetRequest> queryFreqs = request.getQueryFacets();
this.fieldFacetExpressions = new TreeMap<>();
this.rangeFacetExpressions = new LinkedHashMap<>(rangeFreqs.size());
this.queryFacetExpressions = new LinkedHashMap<>(queryFreqs.size());
this.fieldFacetCollectors = new LinkedHashMap<>(fieldFreqs.size());
this.rangeFacetCollectors = new LinkedHashMap<>(rangeFreqs.size());
this.queryFacetCollectors = new LinkedHashMap<>(queryFreqs.size());
this.facetAccumulators = new ArrayList<>();
this.hiddenFieldFacets = new HashSet<>();
/**
* For each field facet request add a bucket to the {@link Expression} map and {@link StatsCollector} map.
* Field facets are computed during the initial collection of documents, therefore
* the FieldFacetAccumulators are created initially.
*/
for( FieldFacetRequest freq : fieldFreqs ){
final FieldFacetRequest fr = (FieldFacetRequest) freq;
if (fr.isHidden()) {
hiddenFieldFacets.add(fr.getName());
}
final SchemaField ff = fr.getField();
final FieldFacetAccumulator facc = FieldFacetAccumulator.create(searcher, this, ff);
facetAccumulators.add(facc);
fieldFacetExpressions.put(freq.getName(), new TreeMap<String, Expression[]>() );
fieldFacetCollectors.put(freq.getName(), new TreeMap<String,StatsCollector[]>());
}
/**
* For each range and query facet request add a bucket to the corresponding
* {@link Expression} map and {@link StatsCollector} map.
* Range and Query Facets are computed in the post processing, so the accumulators
* are not created initially.
*/
for( RangeFacetRequest freq : rangeFreqs ){
if( rangeFacets == null ) rangeFacets = new ArrayList<>();
rangeFacets.add(freq);
rangeFacetExpressions.put(freq.getName(), new LinkedHashMap<String,Expression[]>() );
rangeFacetCollectors.put(freq.getName(), new LinkedHashMap<String,StatsCollector[]>());
}
for( QueryFacetRequest freq : queryFreqs ){
if( queryFacets == null ) queryFacets = new ArrayList<>();
queryFacets.add(freq);
queryFacetExpressions.put(freq.getName(), new LinkedHashMap<String,Expression[]>() );
queryFacetCollectors.put(freq.getName(), new LinkedHashMap<String,StatsCollector[]>());
}
this.queryCount = 0l;
}
public static FacetingAccumulator create(SolrIndexSearcher searcher, DocSet docs, AnalyticsRequest request, SolrQueryRequest queryRequest) throws IOException {
return new FacetingAccumulator(searcher,docs,request,queryRequest);
}
/**
* Update the readers for the {@link BasicAccumulator}, field facets and field facet {@link StatsCollector}s.
* @param context The context to read documents from.
* @throws IOException if there is an error setting the next reader
*/
@Override
protected void doSetNextReader(LeafReaderContext context) throws IOException {
super.doSetNextReader(context);
for( Map<String,StatsCollector[]> valueList : fieldFacetCollectors.values() ){
for (StatsCollector[] statsCollectorList : valueList.values()) {
for (StatsCollector statsCollector : statsCollectorList) {
statsCollector.setNextReader(context);
}
}
}
for (FieldFacetAccumulator fa : facetAccumulators) {
fa.getLeafCollector(context);
}
}
/**
* Updates the reader for all of the range facet {@link StatsCollector}s.
* @param context The context to read documents from.
* @throws IOException if there is an error setting the next reader
*/
public void setRangeStatsCollectorReaders(LeafReaderContext context) throws IOException {
super.getLeafCollector(context);
for( Map<String,StatsCollector[]> rangeList : rangeFacetCollectors.values() ){
for (StatsCollector[] statsCollectorList : rangeList.values()) {
for (StatsCollector statsCollector : statsCollectorList) {
statsCollector.setNextReader(context);
}
}
}
}
/**
* Updates the reader for all of the query facet {@link StatsCollector}s.
* @param context The context to read documents from.
* @throws IOException if there is an error setting the next reader
*/
public void setQueryStatsCollectorReaders(LeafReaderContext context) throws IOException {
super.getLeafCollector(context);
for( Map<String,StatsCollector[]> queryList : queryFacetCollectors.values() ){
for (StatsCollector[] statsCollectorList : queryList.values()) {
for (StatsCollector statsCollector : statsCollectorList) {
statsCollector.setNextReader(context);
}
}
}
}
/**
* Called from Analytics stats, adds documents to the field
* facets and the super {@link BasicAccumulator}.
*/
@Override
public void collect(int doc) throws IOException {
for( FieldFacetAccumulator fa : facetAccumulators ){
fa.collect(doc);
}
super.collect(doc);
}
/**
* Given a document, fieldFacet field and facetValue, adds the document to the
* {@link StatsCollector}s held in the bucket corresponding to the fieldFacet field and facetValue.
* Called during initial document collection.
*/
@Override
public void collectField(int doc, String facetField, String facetValue) throws IOException {
Map<String,StatsCollector[]> map = fieldFacetCollectors.get(facetField);
StatsCollector[] statsCollectors = map.get(facetValue);
// If the facetValue has not been seen yet, a StatsCollector array is
// created and associated with that bucket.
if( statsCollectors == null ){
statsCollectors = statsCollectorArraySupplier.get();
map.put(facetValue,statsCollectors);
fieldFacetExpressions.get(facetField).put(facetValue,makeExpressions(statsCollectors));
for (StatsCollector statsCollector : statsCollectors) {
statsCollector.setNextReader(context);
}
}
for (StatsCollector statsCollector : statsCollectors) {
statsCollector.collect(doc);
}
}
/**
* Given a document, rangeFacet field and range, adds the document to the
* {@link StatsCollector}s held in the bucket corresponding to the rangeFacet field and range.
* Called during post processing.
*/
@Override
public void collectRange(int doc, String facetField, String range) throws IOException {
Map<String,StatsCollector[]> map = rangeFacetCollectors.get(facetField);
StatsCollector[] statsCollectors = map.get(range);
// If the range has not been seen yet, a StatsCollector array is
// created and associated with that bucket.
if( statsCollectors == null ){
statsCollectors = statsCollectorArraySupplier.get();
map.put(range,statsCollectors);
rangeFacetExpressions.get(facetField).put(range,makeExpressions(statsCollectors));
for (StatsCollector statsCollector : statsCollectors) {
statsCollector.setNextReader(context);
}
}
for (StatsCollector statsCollector : statsCollectors) {
statsCollector.collect(doc);
}
}
/**
* Given a document, queryFacet name and query, adds the document to the
* {@link StatsCollector}s held in the bucket corresponding to the queryFacet name and query.
* Called during post processing.
*/
@Override
public void collectQuery(int doc, String facetName, String query) throws IOException {
Map<String,StatsCollector[]> map = queryFacetCollectors.get(facetName);
StatsCollector[] statsCollectors = map.get(query);
// If the query has not been seen yet, a StatsCollector array is
// created and associated with that bucket.
if( statsCollectors == null ){
statsCollectors = statsCollectorArraySupplier.get();
map.put(query,statsCollectors);
queryFacetExpressions.get(facetName).put(query,makeExpressions(statsCollectors));
for (StatsCollector statsCollector : statsCollectors) {
statsCollector.setNextReader(context);
}
}
for (StatsCollector statsCollector : statsCollectors) {
statsCollector.collect(doc);
}
}
/**
* A comparator to compare expression values for field facet sorting.
*/
public static class EntryComparator implements Comparator<Entry<String,Expression[]>> {
private final Comparator<Expression> comp;
private final int comparatorExpressionPlace;
public EntryComparator(Comparator<Expression> comp, int comparatorExpressionPlace) {
this.comp = comp;
this.comparatorExpressionPlace = comparatorExpressionPlace;
}
@Override
public int compare(Entry<String,Expression[]> o1, Entry<String,Expression[]> o2) {
return comp.compare(o1.getValue()[comparatorExpressionPlace], o2.getValue()[comparatorExpressionPlace]);
}
}
/**
* Finalizes the statistics within the each facet bucket before exporting;
*/
@Override
public void compute() {
if (!basicsAndFieldFacetsComputed) {
super.compute();
for( Map<String, StatsCollector[]> f : fieldFacetCollectors.values() ){
for( StatsCollector[] arr : f.values() ){
for( StatsCollector b : arr ){
b.compute();
}
}
}
basicsAndFieldFacetsComputed = true;
}
}
/**
* Finalizes the statistics within the a specific query facet before exporting;
*/
public void computeQueryFacet(String facet) {
Map<String, StatsCollector[]> f = queryFacetCollectors.get(facet);
for( StatsCollector[] arr : f.values() ){
for( StatsCollector b : arr ){
b.compute();
}
}
}
/**
* Finalizes the statistics within the a specific range facet before exporting;
*/
public void computeRangeFacet(String facet) {
Map<String, StatsCollector[]> f = rangeFacetCollectors.get(facet);
for( StatsCollector[] arr : f.values() ){
for( StatsCollector b : arr ){
b.compute();
}
}
}
/**
* Returns the value of an expression to use in a range or query facet.
* @param expressionName the name of the expression
* @param fieldFacet the facet field
* @param facetValue the facet value
* @return String String representation of pivot value
*/
@SuppressWarnings({ "deprecation", "rawtypes" })
public String getResult(String expressionName, String fieldFacet, String facetValue) {
if (facetValue.contains(AnalyticsParams.RESULT) && !facetValue.contains(AnalyticsParams.QUERY_RESULT)) {
try {
String[] pivotStr = ExpressionFactory.getArguments(facetValue.substring(facetValue.indexOf('(')+1,facetValue.lastIndexOf(')')).trim());
if (pivotStr.length==1) {
facetValue = getResult(pivotStr[0]);
} else if (pivotStr.length==3) {
facetValue = getResult(pivotStr[0],pivotStr[1],pivotStr[2]);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+facetValue+" has an invalid amount of arguments.");
}
} catch (IndexOutOfBoundsException e) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+facetValue+" is invalid. Lacks parentheses.",e);
}
}
if (fieldFacetExpressions.get(fieldFacet)!=null) {
Expression[] facetExpressions = fieldFacetExpressions.get(fieldFacet).get(facetValue);
for (int count = 0; count < expressionNames.length; count++) {
if (expressionName.equals(expressionNames[count])) {
Comparable value = facetExpressions[count].getValue();
if (value.getClass().equals(Date.class)) {
return ((Date)value).toInstant().toString();
} else {
return value.toString();
}
}
}
}
throw new SolrException(ErrorCode.BAD_REQUEST,"Field Facet Pivot expression "+expressionName+" not found.");
}
/**
* Returns the value of an expression to use in a range or query facet.
* @param currentFacet the name of the current facet
* @param expressionName the name of the expression
* @param queryFacet the facet query
* @param facetValue the field value
* @return String String representation of pivot value
*/
@SuppressWarnings({ "deprecation", "rawtypes" })
public String getQueryResult(String currentFacet, String expressionName, String queryFacet, String facetValue) {
if (facetValue.contains(AnalyticsParams.RESULT) && !facetValue.contains(AnalyticsParams.QUERY_RESULT)) {
try {
String[] pivotStr = ExpressionFactory.getArguments(facetValue.substring(facetValue.indexOf('(')+1,facetValue.lastIndexOf(')')).trim());
if (pivotStr.length==1) {
facetValue = getResult(pivotStr[0]);
} else if (pivotStr.length==3) {
facetValue = getResult(pivotStr[0],pivotStr[1],pivotStr[2]);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+facetValue+" has an invalid amount of arguments.");
}
} catch (IndexOutOfBoundsException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Result request "+facetValue+" is invalid. Lacks parentheses.",e);
}
}
if (facetValue.contains(AnalyticsParams.QUERY_RESULT)) {
try {
String[] pivotStr = ExpressionFactory.getArguments(facetValue.substring(facetValue.indexOf('(')+1,facetValue.lastIndexOf(')')).trim());
if (pivotStr.length==1) {
facetValue = getResult(pivotStr[0]);
} else if (pivotStr.length==3) {
facetValue = getQueryResult(currentFacet,pivotStr[0],pivotStr[1],pivotStr[2]);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST,"Result request "+facetValue+" has an invalid amount of arguments.");
}
} catch (IndexOutOfBoundsException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Result request "+facetValue+" is invalid. Lacks parentheses.",e);
}
}
if (queryFacetExpressions.get(queryFacet)!=null) {
Expression[] facetExpressions = queryFacetExpressions.get(queryFacet).get(facetValue);
for (int count = 0; count < expressionNames.length; count++) {
if (expressionName.equals(expressionNames[count])) {
Comparable value = facetExpressions[count].getValue();
if (value.getClass().equals(Date.class)) {
return ((Date)value).toInstant().toString();
} else {
return value.toString();
}
}
}
}
throw new SolrException(ErrorCode.BAD_REQUEST,"Field Facet Pivot expression "+expressionName+" not found.");
}
@Override
@SuppressWarnings("unchecked")
public NamedList<?> export() {
final NamedList<Object> base = (NamedList<Object>)super.export();
NamedList<NamedList<?>> facetList = new NamedList<>();
// Add the field facet buckets to the output
base.add("fieldFacets",facetList);
for( FieldFacetRequest freq : request.getFieldFacets() ){
final String name = freq.getName();
if (hiddenFieldFacets.contains(name)) {
continue;
}
final Map<String,Expression[]> buckets = fieldFacetExpressions.get(name);
final NamedList<Object> bucketBase = new NamedList<>();
Iterable<Entry<String,Expression[]>> iter = buckets.entrySet();
final FieldFacetRequest fr = (FieldFacetRequest) freq;
final FacetSortSpecification sort = fr.getSort();
final int limit = fr.getLimit();
final int offset = fr.getOffset();
final boolean showMissing = fr.showsMissing();
if (!showMissing) {
buckets.remove(MISSING_VALUE);
}
// Sorting the buckets if a sort specification is provided
if( sort != null && buckets.values().iterator().hasNext()){
int sortPlace = Arrays.binarySearch(expressionNames, sort.getStatistic());
final Expression first = buckets.values().iterator().next()[sortPlace];
final Comparator<Expression> comp = (Comparator<Expression>) first.comparator(sort.getDirection());
final List<Entry<String,Expression[]>> sorted = new ArrayList<>(buckets.size());
Iterables.addAll(sorted, iter);
Collections.sort(sorted, new EntryComparator(comp,sortPlace));
iter = sorted;
}
// apply the limit
if( limit > AnalyticsContentHandler.DEFAULT_FACET_LIMIT ){
if( offset > 0 ){
iter = Iterables.skip(iter, offset);
}
iter = Iterables.limit(iter, limit);
}
// Export each expression in the bucket.
for( Entry<String,Expression[]> bucket : iter ){
bucketBase.add(bucket.getKey(),export(bucket.getValue()));
}
facetList.add(name, bucketBase);
}
// Add the range facet buckets to the output
facetList = new NamedList<>();
base.add("rangeFacets",facetList);
for( RangeFacetRequest freq : request.getRangeFacets() ){
final String name = freq.getName();
final Map<String,Expression[]> buckets = rangeFacetExpressions.get(name);
final NamedList<Object> bucketBase = new NamedList<>();
Iterable<Entry<String,Expression[]>> iter = buckets.entrySet();
for( Entry<String,Expression[]> bucket : iter ){
bucketBase.add(bucket.getKey(),export(bucket.getValue()));
}
facetList.add(name, bucketBase);
}
// Add the query facet buckets to the output
facetList = new NamedList<>();
base.add("queryFacets",facetList);
for( QueryFacetRequest freq : request.getQueryFacets() ){
final String name = freq.getName();
final Map<String,Expression[]> buckets = queryFacetExpressions.get(name);
final NamedList<Object> bucketBase = new NamedList<>();
Iterable<Entry<String,Expression[]>> iter = buckets.entrySet();
for( Entry<String,Expression[]> bucket : iter ){
bucketBase.add(bucket.getKey(),export(bucket.getValue()));
}
facetList.add(name, bucketBase);
}
return base;
}
/**
* Exports a list of expressions as a NamedList
* @param expressionArr an array of expressions
* @return named list of expressions
*/
public NamedList<?> export(Expression[] expressionArr) {
NamedList<Object> base = new NamedList<>();
for (int count = 0; count < expressionArr.length; count++) {
if (!hiddenExpressions.contains(expressionNames[count])) {
base.add(expressionNames[count], expressionArr[count].getValue());
}
}
return base;
}
/**
* Processes the query and range facets.
* Must be called if range and/or query facets are supported.
*/
@Override
public void postProcess() throws IOException {
super.compute();
for( Map<String, StatsCollector[]> f : fieldFacetCollectors.values() ){
for( StatsCollector[] arr : f.values() ){
for( StatsCollector b : arr ){
b.compute();
}
}
}
basicsAndFieldFacetsComputed = true;
final Filter filter = docs.getTopFilter();
if( rangeFacets != null ){
processRangeFacets(filter);
}
if( queryFacets != null ){
processQueryFacets(filter);
}
}
/**
* Initiates the collecting of query facets
* @param filter the base filter to work against
* @throws IOException if searching failed
*/
public void processQueryFacets(final Filter filter) throws IOException {
for( QueryFacetRequest qfr : queryFacets ){
for( String query : qfr.getQueries() ){
if (query.contains(AnalyticsParams.RESULT) && !query.contains(AnalyticsParams.QUERY_RESULT)) {
try {
String[] pivotStr = ExpressionFactory.getArguments(query.substring(query.indexOf('(')+1,query.lastIndexOf(')')).trim());
if (pivotStr.length==1) {
query = getResult(pivotStr[0]);
} else if (pivotStr.length==3) {
query = getResult(pivotStr[0],pivotStr[1],pivotStr[2]);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST,"Result request "+query+" has an invalid amount of arguments.");
}
} catch (IndexOutOfBoundsException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Result request "+query+" is invalid. Lacks parentheses.",e);
}
} else if (query.contains(AnalyticsParams.QUERY_RESULT)) {
try {
String[] pivotStr = ExpressionFactory.getArguments(query.substring(query.indexOf('(')+1,query.lastIndexOf(')')).trim());
if (pivotStr.length==3) {
query = getQueryResult(qfr.getName(),pivotStr[0],pivotStr[1],pivotStr[2]);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST,"Result request "+query+" has an invalid amount of arguments.");
}
} catch (IndexOutOfBoundsException e) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Result request "+query+" is invalid. Lacks parentheses.",e);
}
}
QueryFacetAccumulator qAcc = new QueryFacetAccumulator(this,qfr.getName(),query);
final Query q;
try {
q = QParser.getParser(query, queryRequest).getQuery();
} catch( SyntaxError e ){
throw new SolrException(ErrorCode.BAD_REQUEST,"Invalid query '"+query+"'",e);
}
// The searcher sends docIds to the QueryFacetAccumulator which forwards
// them to <code>collectQuery()</code> in this class for collection.
Query filtered = new BooleanQuery.Builder()
.add(q, Occur.MUST)
.add(filter, Occur.FILTER)
.build();
searcher.search(filtered, qAcc);
computeQueryFacet(qfr.getName());
queryCount++;
}
}
}
@Override
public long getNumQueries() {
return queryCount;
}
/**
* Initiates the collecting of range facets
* @param filter the base filter to use
* @throws IOException if searching fails
*/
public void processRangeFacets(final Filter filter) throws IOException {
for( RangeFacetRequest rfr : rangeFacets ){
String[] pivotStr;
String start = rfr.getStart();
if (start.contains(AnalyticsParams.QUERY_RESULT)) {
throw new SolrException(ErrorCode.BAD_REQUEST,"Query result requests can not be used in Range Facets");
} else if (start.contains(AnalyticsParams.RESULT)) {
try {
pivotStr = ExpressionFactory.getArguments(start.substring(start.indexOf('(')+1,start.indexOf(')')).trim());
if (pivotStr.length==1) {
rfr.setStart(getResult(pivotStr[0]));
} else if (pivotStr.length==3) {
rfr.setStart(getResult(pivotStr[0],pivotStr[1],pivotStr[2]));
} else {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+start+" has an invalid amount of arguments.");
}
} catch (IndexOutOfBoundsException e) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+start+" is invalid. Lacks parentheses.",e);
}
}
String end = rfr.getEnd();
if (end.contains(AnalyticsParams.QUERY_RESULT)) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Query result requests can not be used in Range Facets");
} else if (end.contains(AnalyticsParams.RESULT)) {
try {
pivotStr = ExpressionFactory.getArguments(end.substring(end.indexOf('(')+1,end.indexOf(')')).trim());
if (pivotStr.length==1) {
rfr.setEnd(getResult(pivotStr[0]));
} else if (pivotStr.length==3) {
rfr.setEnd(getResult(pivotStr[0],pivotStr[1],pivotStr[2]));
} else {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+end+" has an invalid amount of arguments.");
}
} catch (IndexOutOfBoundsException e) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+end+" is invalid. Lacks parentheses.",e);
}
}
String[] gaps = rfr.getGaps();
for (int count = 0; count<gaps.length; count++){
String gap = gaps[count];
if (gap.contains(AnalyticsParams.QUERY_RESULT)) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Query result requests can not be used in Range Facets");
} else if (gap.contains(AnalyticsParams.RESULT)) {
try {
pivotStr = ExpressionFactory.getArguments(gap.substring(gap.indexOf('(')+1,gap.indexOf(')')).trim());
if (pivotStr.length==1) {
gaps[count]=getResult(pivotStr[0]);
} else if (pivotStr.length==3) {
gaps[count]=getResult(pivotStr[0],pivotStr[1],pivotStr[2]);
} else {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+gap+" has an invalid amount of arguments.");
}
} catch (IndexOutOfBoundsException e) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Result request "+gap+" is invalid. Lacks parentheses.",e);
}
}
}
// Computes the end points of the ranges in the rangeFacet
final RangeEndpointCalculator<? extends Comparable<?>> rec = RangeEndpointCalculator.create(rfr);
final SchemaField sf = rfr.getField();
// Create a rangeFacetAccumulator for each range and
// collect the documents for that range.
for( FacetRange range : rec.getRanges() ){
final String upper;
final String lower;
String facetValue = "";
if( range.lower == null ){
facetValue = "(*";
lower = null;
} else {
lower = range.lower;
facetValue = ((range.includeLower)?"[":"(") + range.lower;
}
facetValue+=" TO ";
if( range.upper == null ){
upper = null;
facetValue += "*)";
} else {
upper = range.upper;
facetValue += range.upper + ((range.includeUpper)?"]":")");
}
Query q = sf.getType().getRangeQuery(null, sf, lower, upper, range.includeLower,range.includeUpper);
RangeFacetAccumulator rAcc = new RangeFacetAccumulator(this,rfr.getName(),facetValue);
// The searcher sends docIds to the RangeFacetAccumulator which forwards
// them to <code>collectRange()</code> in this class for collection.
Query filtered = new BooleanQuery.Builder()
.add(q, Occur.MUST)
.add(filter, Occur.FILTER)
.build();
searcher.search(filtered, rAcc);
computeRangeFacet(sf.getName());
}
}
}
}