You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dp...@apache.org on 2017/06/28 18:17:43 UTC
[20/20] lucene-solr:master: SOLR-10123: Upgraded the Analytics
Component to version 2.0
SOLR-10123: Upgraded the Analytics Component to version 2.0
Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/d5963beb
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/d5963beb
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/d5963beb
Branch: refs/heads/master
Commit: d5963bebc43bdb712f2f1e9a29f944370f079b5f
Parents: 85a27a2
Author: Dennis Gove <dp...@gmail.com>
Authored: Wed Jun 28 09:39:47 2017 -0400
Committer: Dennis Gove <dp...@gmail.com>
Committed: Wed Jun 28 14:13:37 2017 -0400
----------------------------------------------------------------------
solr/CHANGES.txt | 8 +
.../apache/solr/analytics/AnalyticsDriver.java | 82 +
.../solr/analytics/AnalyticsExpression.java | 64 +
.../analytics/AnalyticsGroupingManager.java | 239 ++
.../solr/analytics/AnalyticsRequestManager.java | 279 ++
.../solr/analytics/AnalyticsRequestParser.java | 549 +++
.../solr/analytics/ExpressionFactory.java | 821 +++++
.../analytics/accumulator/BasicAccumulator.java | 173 -
.../accumulator/FacetingAccumulator.java | 730 ----
.../analytics/accumulator/ValueAccumulator.java | 40 -
.../facet/FacetValueAccumulator.java | 35 -
.../facet/FieldFacetAccumulator.java | 153 -
.../facet/QueryFacetAccumulator.java | 72 -
.../facet/RangeFacetAccumulator.java | 49 -
.../accumulator/facet/package-info.java | 24 -
.../analytics/accumulator/package-info.java | 23 -
.../analytics/expression/BaseExpression.java | 88 -
.../expression/DualDelegateExpression.java | 99 -
.../solr/analytics/expression/Expression.java | 39 -
.../analytics/expression/ExpressionFactory.java | 175 -
.../expression/MultiDelegateExpression.java | 131 -
.../expression/SingleDelegateExpression.java | 88 -
.../solr/analytics/expression/package-info.java | 23 -
.../analytics/facet/AbstractSolrQueryFacet.java | 104 +
.../solr/analytics/facet/AnalyticsFacet.java | 166 +
.../apache/solr/analytics/facet/PivotFacet.java | 114 +
.../apache/solr/analytics/facet/PivotNode.java | 263 ++
.../apache/solr/analytics/facet/QueryFacet.java | 64 +
.../apache/solr/analytics/facet/RangeFacet.java | 119 +
.../solr/analytics/facet/SortableFacet.java | 178 +
.../solr/analytics/facet/StreamingFacet.java | 32 +
.../apache/solr/analytics/facet/ValueFacet.java | 60 +
.../facet/compare/ConstantComparator.java | 30 +
.../facet/compare/DelegatingComparator.java | 62 +
.../facet/compare/ExpressionComparator.java | 46 +
.../facet/compare/FacetResultsComparator.java | 52 +
.../facet/compare/FacetValueComparator.java | 37 +
.../analytics/facet/compare/package-info.java | 23 +
.../solr/analytics/facet/package-info.java | 23 +
.../function/ExpressionCalculator.java | 71 +
.../MergingReductionCollectionManager.java | 46 +
.../function/ReductionCollectionManager.java | 320 ++
.../analytics/function/ReductionFunction.java | 37 +
.../function/field/AnalyticsField.java | 69 +
.../analytics/function/field/BooleanField.java | 111 +
.../function/field/BooleanMultiField.java | 101 +
.../analytics/function/field/DateField.java | 108 +
.../function/field/DateMultiField.java | 47 +
.../function/field/DateMultiPointField.java | 47 +
.../analytics/function/field/DoubleField.java | 97 +
.../function/field/DoubleMultiField.java | 85 +
.../function/field/DoubleMultiPointField.java | 81 +
.../analytics/function/field/FloatField.java | 108 +
.../function/field/FloatMultiField.java | 91 +
.../function/field/FloatMultiPointField.java | 87 +
.../solr/analytics/function/field/IntField.java | 129 +
.../analytics/function/field/IntMultiField.java | 100 +
.../function/field/IntMultiPointField.java | 96 +
.../analytics/function/field/LongField.java | 107 +
.../function/field/LongMultiField.java | 89 +
.../function/field/LongMultiPointField.java | 86 +
.../analytics/function/field/StringField.java | 85 +
.../function/field/StringMultiField.java | 66 +
.../analytics/function/field/package-info.java | 23 +
.../function/mapping/AbsoluteValueFunction.java | 54 +
.../analytics/function/mapping/AddFunction.java | 68 +
.../function/mapping/BottomFunction.java | 163 +
.../function/mapping/CompareFunction.java | 614 ++++
.../function/mapping/ConcatFunction.java | 78 +
.../function/mapping/DateMathFunction.java | 156 +
.../function/mapping/DateParseFunction.java | 210 ++
.../function/mapping/DivideFunction.java | 51 +
.../function/mapping/FillMissingFunction.java | 842 +++++
.../function/mapping/FilterFunction.java | 722 ++++
.../analytics/function/mapping/IfFunction.java | 892 +++++
.../function/mapping/JoinFunction.java | 57 +
.../function/mapping/LambdaFunction.java | 3220 ++++++++++++++++++
.../analytics/function/mapping/LogFunction.java | 51 +
.../function/mapping/LogicFunction.java | 90 +
.../function/mapping/MultFunction.java | 68 +
.../function/mapping/NegateFunction.java | 58 +
.../mapping/NumericConvertFunction.java | 256 ++
.../function/mapping/PowerFunction.java | 51 +
.../function/mapping/RemoveFunction.java | 796 +++++
.../function/mapping/ReplaceFunction.java | 914 +++++
.../function/mapping/StringCastFunction.java | 42 +
.../function/mapping/SubtractFunction.java | 51 +
.../analytics/function/mapping/TopFunction.java | 163 +
.../function/mapping/package-info.java | 23 +
.../solr/analytics/function/package-info.java | 23 +
.../function/reduction/CountFunction.java | 87 +
.../function/reduction/DocCountFunction.java | 87 +
.../function/reduction/MaxFunction.java | 298 ++
.../function/reduction/MedianFunction.java | 200 ++
.../function/reduction/MinFunction.java | 298 ++
.../function/reduction/MissingFunction.java | 76 +
.../function/reduction/OrdinalFunction.java | 386 +++
.../function/reduction/PercentileFunction.java | 337 ++
.../function/reduction/SumFunction.java | 92 +
.../function/reduction/UniqueFunction.java | 101 +
.../function/reduction/data/CountCollector.java | 188 +
.../function/reduction/data/MaxCollector.java | 476 +++
.../function/reduction/data/MinCollector.java | 476 +++
.../function/reduction/data/ReductionData.java | 24 +
.../reduction/data/ReductionDataCollector.java | 183 +
.../reduction/data/SortedListCollector.java | 363 ++
.../function/reduction/data/SumCollector.java | 124 +
.../reduction/data/UniqueCollector.java | 241 ++
.../function/reduction/data/package-info.java | 24 +
.../function/reduction/package-info.java | 23 +
.../org/apache/solr/analytics/package-info.java | 23 +
.../request/AbstractFieldFacetRequest.java | 42 -
.../request/AnalyticsContentHandler.java | 314 --
.../analytics/request/AnalyticsRequest.java | 114 -
.../request/AnalyticsRequestFactory.java | 308 --
.../solr/analytics/request/AnalyticsStats.java | 138 -
.../analytics/request/ExpressionRequest.java | 72 -
.../solr/analytics/request/FacetRequest.java | 26 -
.../analytics/request/FieldFacetRequest.java | 172 -
.../analytics/request/QueryFacetRequest.java | 74 -
.../analytics/request/RangeFacetRequest.java | 129 -
.../solr/analytics/request/package-info.java | 24 -
.../AbstractDelegatingStatsCollector.java | 74 -
.../statistics/MedianStatsCollector.java | 76 -
.../statistics/MinMaxStatsCollector.java | 114 -
.../statistics/NumericStatsCollector.java | 68 -
.../statistics/PercentileStatsCollector.java | 80 -
.../analytics/statistics/StatsCollector.java | 69 -
.../StatsCollectorSupplierFactory.java | 646 ----
.../statistics/UniqueStatsCollector.java | 53 -
.../solr/analytics/statistics/package-info.java | 24 -
.../stream/AnalyticsShardRequestManager.java | 245 ++
.../stream/AnalyticsShardResponseParser.java | 89 +
.../solr/analytics/stream/package-info.java | 23 +
.../reservation/BooleanArrayReservation.java | 44 +
.../reservation/BooleanCheckedReservation.java | 42 +
.../stream/reservation/BooleanReservation.java | 42 +
.../reservation/DoubleArrayReservation.java | 44 +
.../reservation/DoubleCheckedReservation.java | 43 +
.../stream/reservation/DoubleReservation.java | 42 +
.../reservation/FloatArrayReservation.java | 44 +
.../reservation/FloatCheckedReservation.java | 43 +
.../stream/reservation/FloatReservation.java | 42 +
.../stream/reservation/IntArrayReservation.java | 42 +
.../reservation/IntCheckedReservation.java | 43 +
.../stream/reservation/IntReservation.java | 42 +
.../reservation/LongArrayReservation.java | 45 +
.../reservation/LongCheckedReservation.java | 43 +
.../stream/reservation/LongReservation.java | 42 +
.../ReductionCheckedDataReservation.java | 35 +
.../ReductionDataArrayReservation.java | 36 +
.../reservation/ReductionDataReservation.java | 53 +
.../reservation/StringArrayReservation.java | 45 +
.../reservation/StringCheckedReservation.java | 44 +
.../stream/reservation/StringReservation.java | 43 +
.../stream/reservation/package-info.java | 24 +
.../read/BooleanCheckedDataReader.java | 33 +
.../read/BooleanDataArrayReader.java | 36 +
.../reservation/read/BooleanDataReader.java | 33 +
.../read/DoubleCheckedDataReader.java | 32 +
.../reservation/read/DoubleDataArrayReader.java | 35 +
.../reservation/read/DoubleDataReader.java | 32 +
.../read/FloatCheckedDataReader.java | 33 +
.../reservation/read/FloatDataArrayReader.java | 36 +
.../reservation/read/FloatDataReader.java | 33 +
.../reservation/read/IntCheckedDataReader.java | 32 +
.../reservation/read/IntDataArrayReader.java | 34 +
.../stream/reservation/read/IntDataReader.java | 32 +
.../reservation/read/LongCheckedDataReader.java | 32 +
.../reservation/read/LongDataArrayReader.java | 35 +
.../stream/reservation/read/LongDataReader.java | 32 +
.../read/ReductionCheckedDataReader.java | 54 +
.../read/ReductionDataArrayReader.java | 54 +
.../reservation/read/ReductionDataReader.java | 40 +
.../read/StringCheckedDataReader.java | 32 +
.../reservation/read/StringDataArrayReader.java | 35 +
.../reservation/read/StringDataReader.java | 34 +
.../stream/reservation/read/package-info.java | 24 +
.../write/BooleanCheckedDataWriter.java | 33 +
.../write/BooleanDataArrayWriter.java | 36 +
.../reservation/write/BooleanDataWriter.java | 33 +
.../write/DoubleCheckedDataWriter.java | 34 +
.../write/DoubleDataArrayWriter.java | 36 +
.../reservation/write/DoubleDataWriter.java | 33 +
.../write/FloatCheckedDataWriter.java | 35 +
.../reservation/write/FloatDataArrayWriter.java | 37 +
.../reservation/write/FloatDataWriter.java | 34 +
.../reservation/write/IntCheckedDataWriter.java | 34 +
.../reservation/write/IntDataArrayWriter.java | 35 +
.../stream/reservation/write/IntDataWriter.java | 33 +
.../write/LongCheckedDataWriter.java | 34 +
.../reservation/write/LongDataArrayWriter.java | 36 +
.../reservation/write/LongDataWriter.java | 33 +
.../write/ReductionCheckedDataWriter.java | 60 +
.../write/ReductionDataArrayWriter.java | 53 +
.../reservation/write/ReductionDataWriter.java | 40 +
.../write/StringCheckedDataWriter.java | 34 +
.../write/StringDataArrayWriter.java | 36 +
.../reservation/write/StringDataWriter.java | 37 +
.../stream/reservation/write/package-info.java | 24 +
.../solr/analytics/util/AnalyticsParams.java | 114 -
.../solr/analytics/util/AnalyticsParsers.java | 171 -
.../util/AnalyticsResponseHeadings.java | 36 +
.../analytics/util/FacetRangeGenerator.java | 356 ++
.../solr/analytics/util/MedianCalculator.java | 4 +
.../solr/analytics/util/OldAnalyticsParams.java | 177 +
.../util/OldAnalyticsRequestConverter.java | 177 +
.../solr/analytics/util/OrdinalCalculator.java | 173 +
.../analytics/util/PercentileCalculator.java | 176 -
.../analytics/util/RangeEndpointCalculator.java | 354 --
.../util/function/BooleanConsumer.java | 59 +
.../analytics/util/function/FloatConsumer.java | 59 +
.../analytics/util/function/FloatSupplier.java | 41 +
.../analytics/util/function/package-info.java | 23 +
.../solr/analytics/util/package-info.java | 3 +-
.../AbsoluteValueDoubleFunction.java | 60 -
.../util/valuesource/AddDoubleFunction.java | 49 -
.../util/valuesource/ConstDateSource.java | 112 -
.../util/valuesource/ConstDoubleSource.java | 104 -
.../util/valuesource/ConstStringSource.java | 50 -
.../util/valuesource/DateFieldSource.java | 131 -
.../util/valuesource/DateMathFunction.java | 71 -
.../util/valuesource/DivDoubleFunction.java | 48 -
.../util/valuesource/DualDoubleFunction.java | 94 -
.../util/valuesource/FilterFieldSource.java | 154 -
.../util/valuesource/LogDoubleFunction.java | 43 -
.../util/valuesource/MultiDateFunction.java | 133 -
.../util/valuesource/MultiDoubleFunction.java | 119 -
.../valuesource/MultiplyDoubleFunction.java | 49 -
.../util/valuesource/NegateDoubleFunction.java | 55 -
.../util/valuesource/PowDoubleFunction.java | 48 -
.../util/valuesource/ReverseStringFunction.java | 45 -
.../util/valuesource/SingleDoubleFunction.java | 79 -
.../util/valuesource/SingleStringFunction.java | 117 -
.../util/valuesource/package-info.java | 24 -
.../solr/analytics/value/AnalyticsValue.java | 55 +
.../analytics/value/AnalyticsValueStream.java | 133 +
.../solr/analytics/value/BooleanValue.java | 85 +
.../analytics/value/BooleanValueStream.java | 55 +
.../solr/analytics/value/ComparableValue.java | 32 +
.../apache/solr/analytics/value/DateValue.java | 102 +
.../solr/analytics/value/DateValueStream.java | 62 +
.../solr/analytics/value/DoubleValue.java | 86 +
.../solr/analytics/value/DoubleValueStream.java | 54 +
.../apache/solr/analytics/value/FloatValue.java | 97 +
.../solr/analytics/value/FloatValueStream.java | 60 +
.../apache/solr/analytics/value/IntValue.java | 121 +
.../solr/analytics/value/IntValueStream.java | 71 +
.../apache/solr/analytics/value/LongValue.java | 97 +
.../solr/analytics/value/LongValueStream.java | 60 +
.../solr/analytics/value/StringValue.java | 71 +
.../solr/analytics/value/StringValueStream.java | 49 +
.../value/constant/ConstantBooleanValue.java | 91 +
.../value/constant/ConstantDateValue.java | 103 +
.../value/constant/ConstantDoubleValue.java | 90 +
.../value/constant/ConstantFloatValue.java | 99 +
.../value/constant/ConstantIntValue.java | 118 +
.../value/constant/ConstantLongValue.java | 100 +
.../value/constant/ConstantStringValue.java | 79 +
.../analytics/value/constant/ConstantValue.java | 128 +
.../analytics/value/constant/package-info.java | 23 +
.../solr/analytics/value/package-info.java | 23 +
.../apache/solr/handler/AnalyticsHandler.java | 147 +
.../handler/component/AnalyticsComponent.java | 119 +-
.../java/org/apache/solr/handler/package.html | 28 +
.../response/AnalyticsShardResponseWriter.java | 91 +
.../java/org/apache/solr/response/package.html | 28 +
.../analytics/requestFiles/expressions.txt | 70 -
.../analytics/requestFiles/fieldFacetExtras.txt | 66 -
.../analytics/requestFiles/fieldFacets.txt | 132 -
.../analytics/requestFiles/functions.txt | 62 -
.../analytics/requestFiles/noFacets.txt | 74 -
.../analytics/requestFiles/queryFacets.txt | 45 -
.../analytics/requestFiles/rangeFacets.txt | 170 -
.../analytics/requestXMLFiles/expressions.xml | 285 --
.../requestXMLFiles/fieldFacetExtras.xml | 101 -
.../analytics/requestXMLFiles/fieldFacets.xml | 496 ---
.../analytics/requestXMLFiles/functions.xml | 246 --
.../analytics/requestXMLFiles/noFacets.xml | 310 --
.../analytics/requestXMLFiles/queryFacets.xml | 94 -
.../analytics/requestXMLFiles/rangeFacets.xml | 319 --
.../test-files/solr/analytics/expressions.txt | 65 +
.../test-files/solr/analytics/facetSorting.txt | 4 +
.../solr/analytics/fieldFacetExtras.txt | 66 +
.../test-files/solr/analytics/fieldFacets.txt | 132 +
.../src/test-files/solr/analytics/functions.txt | 57 +
.../src/test-files/solr/analytics/noFacets.txt | 74 +
.../test-files/solr/analytics/queryFacets.txt | 27 +
.../test-files/solr/analytics/rangeFacets.txt | 161 +
.../solr/collection1/conf/schema-analytics.xml | 7 +-
.../collection1/conf/solrconfig-analytics.xml | 42 +
.../solr/collection1/conf/solrconfig-basic.xml | 40 -
.../configsets/cloud-analytics/conf/schema.xml | 63 +
.../cloud-analytics/conf/solrconfig.xml | 59 +
.../AbstractAnalyticsStatsCloudTest.java | 187 +
.../analytics/AbstractAnalyticsStatsTest.java | 14 +-
.../apache/solr/analytics/NoFacetCloudTest.java | 557 +++
.../org/apache/solr/analytics/NoFacetTest.java | 46 +-
.../analytics/expression/ExpressionTest.java | 70 +-
.../solr/analytics/expression/FunctionTest.java | 221 ++
.../facet/AbstractAnalyticsFacetCloudTest.java | 284 ++
.../facet/AbstractAnalyticsFacetTest.java | 37 +-
.../solr/analytics/facet/FacetSortingTest.java | 53 +
.../analytics/facet/FieldFacetCloudTest.java | 1214 +++++++
.../facet/FieldFacetExtrasCloudTest.java | 253 ++
.../analytics/facet/FieldFacetExtrasTest.java | 6 +-
.../solr/analytics/facet/FieldFacetTest.java | 63 +-
.../analytics/facet/QueryFacetCloudTest.java | 159 +
.../solr/analytics/facet/QueryFacetTest.java | 10 +-
.../analytics/facet/RangeFacetCloudTest.java | 588 ++++
.../solr/analytics/facet/RangeFacetTest.java | 43 +-
.../util/valuesource/FunctionTest.java | 233 --
.../solr/handler/component/ResponseBuilder.java | 3 +
313 files changed, 30374 insertions(+), 9933 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 208512c..9578698 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -116,6 +116,10 @@ Upgrading from Solr 6.x
curl http://host:8983/solr/mycollection/config -d '{"set-user-property": {"update.autoCreateFields":"false"}}'
Please see SOLR-10574 for details.
+* SOLR-10123: The Analytics Component has been upgraded to support distributed collections, expressions over multivalued
+ fields, a new JSON request language, and more. DocValues are now required for any field used in the analytics expression
+ whereas previously docValues was not required. Please see SOLR-10123 for details.
+
New Features
----------------------
* SOLR-9857, SOLR-9858: Collect aggregated metrics from nodes and shard leaders in overseer. (ab)
@@ -175,6 +179,10 @@ New Features
* SOLR-10272: Use _default config set if no collection.configName is specified with CREATE (Ishan Chattopadhyaya)
+* SOLR-10123: Upgraded the Analytics Component to version 2.0 which now supports distributed collections, expressions over
+ multivalued fields, a new JSON request language, and more. DocValues are now required for any field used in the analytics
+ expression whereas previously docValues was not required. Please see SOLR-10123 for details. (Houston Putman)
+
Bug Fixes
----------------------
* SOLR-9262: Connection and read timeouts are being ignored by UpdateShardHandler after SOLR-4509.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsDriver.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsDriver.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsDriver.java
new file mode 100644
index 0000000..21b053f
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsDriver.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.DocIdSet;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.solr.analytics.AnalyticsRequestManager.StreamingInfo;
+import org.apache.solr.analytics.facet.AbstractSolrQueryFacet.FacetValueQueryExecuter;
+import org.apache.solr.analytics.facet.StreamingFacet;
+import org.apache.solr.analytics.function.ReductionCollectionManager;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.Filter;
+import org.apache.solr.search.SolrIndexSearcher;
+
+public class AnalyticsDriver {
+
+ /**
+ * Drive the collection of reduction data. This includes overall data as well as faceted data.
+ *
+ * @param manager of the request to drive
+ * @param searcher the results of the query
+ * @param filter that represents the overall query
+ * @param queryRequest used for the search request
+ * @throws IOException if an error occurs while reading from Solr
+ */
+ public static void drive(AnalyticsRequestManager manager, SolrIndexSearcher searcher, Filter filter, SolrQueryRequest queryRequest) throws IOException {
+ StreamingInfo streamingInfo = manager.getStreamingFacetInfo();
+ Iterable<StreamingFacet> streamingFacets = streamingInfo.streamingFacets;
+ ReductionCollectionManager collectionManager = streamingInfo.streamingCollectionManager;
+
+ Iterable<FacetValueQueryExecuter> facetExecuters = manager.getFacetExecuters(filter, queryRequest);
+
+ // Streaming phase (Overall results & Value/Pivot Facets)
+ // Loop through all documents and collect reduction data for streaming facets and overall results
+ if (collectionManager.needsCollection()) {
+ List<LeafReaderContext> contexts = searcher.getTopReaderContext().leaves();
+ for (int leafNum = 0; leafNum < contexts.size(); leafNum++) {
+ LeafReaderContext context = contexts.get(leafNum);
+ DocIdSet dis = filter.getDocIdSet(context, null); // solr docsets already exclude any deleted docs
+ if (dis == null) {
+ continue;
+ }
+ DocIdSetIterator disi = dis.iterator();
+ if (disi != null) {
+ collectionManager.doSetNextReader(context);
+ int doc = disi.nextDoc();
+ while( doc != DocIdSetIterator.NO_MORE_DOCS){
+ // Add a document to the statistics being generated
+ collectionManager.collect(doc);
+ streamingFacets.forEach( facet -> facet.addFacetValueCollectionTargets() );
+ collectionManager.apply();
+ doc = disi.nextDoc();
+ }
+ }
+ }
+ }
+
+ // Executing phase (Query/Range Facets)
+ // Send additional Solr Queries to compute facet values
+ for (FacetValueQueryExecuter executer : facetExecuters) {
+ executer.execute(searcher);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsExpression.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsExpression.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsExpression.java
new file mode 100644
index 0000000..044e371
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsExpression.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import org.apache.solr.analytics.function.ReductionCollectionManager;
+import org.apache.solr.analytics.function.ReductionCollectionManager.ReductionDataCollection;
+import org.apache.solr.analytics.value.AnalyticsValue;
+
+/**
+ * A wrapper for a top-level analytics expression.
+ * The expression must have a name and be single valued.
+ */
+public class AnalyticsExpression {
+ private final AnalyticsValue expression;
+ private final String name;
+
+ public AnalyticsExpression(String name, AnalyticsValue expression) {
+ this.name = name;
+ this.expression = expression;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public AnalyticsValue getExpression() {
+ return expression;
+ }
+
+ /**
+ * Get the current value of the expression.
+ * This method can, and will, be called multiple times to return different values.
+ * The value returned is based on the {@link ReductionDataCollection} given
+ * to the {@link ReductionCollectionManager#setData} method.
+ *
+ * @return the current value of the expression
+ */
+ public Object toObject() {
+ return expression.getObject();
+ }
+
+ /**
+ * NOTE: Must be called after {@link #toObject()} is called, otherwise the value is not guaranteed to be correct.
+ *
+ * @return whether the current value of the expression exists.
+ */
+ public boolean exists() {
+ return expression.exists();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsGroupingManager.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsGroupingManager.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsGroupingManager.java
new file mode 100644
index 0000000..a95a451
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsGroupingManager.java
@@ -0,0 +1,239 @@
+/*
+ * 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;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestManager.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestManager.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestManager.java
new file mode 100644
index 0000000..45b958f
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestManager.java
@@ -0,0 +1,279 @@
+/*
+ * 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;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.solr.analytics.facet.AbstractSolrQueryFacet;
+import org.apache.solr.analytics.facet.AbstractSolrQueryFacet.FacetValueQueryExecuter;
+import org.apache.solr.analytics.facet.StreamingFacet;
+import org.apache.solr.analytics.function.ExpressionCalculator;
+import org.apache.solr.analytics.function.ReductionCollectionManager;
+import org.apache.solr.analytics.function.ReductionCollectionManager.ReductionDataCollection;
+import org.apache.solr.analytics.stream.AnalyticsShardRequestManager;
+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 of an entire analytics request.
+ */
+public class AnalyticsRequestManager {
+ private final ReductionCollectionManager ungroupedReductionManager;
+ private ReductionDataCollection ungroupedData;
+
+ private final Map<String, AnalyticsGroupingManager> groupingManagers;
+
+ private final Collection<AnalyticsExpression> ungroupedExpressions;
+ private final ExpressionCalculator ungroupedExpressionCalculator;
+
+ /**
+ * If the request is distributed, the manager for shard requests.
+ */
+ public String analyticsRequest;
+ public AnalyticsShardRequestManager shardStream;
+ public boolean sendShards;
+
+ /**
+ * Create an manager with the given ungrouped expressions. This is straightforward in the new
+ * style of request, however in the old olap-style requests all groupings' expressions are expected
+ * to be ungrouped as well.
+ *
+ *
+ * @param ungroupedReductionManager to manage the reduction collection for all ungrouped expressions
+ * @param ungroupedExpressions to compute overall results for
+ */
+ public AnalyticsRequestManager(ReductionCollectionManager ungroupedReductionManager,
+ Collection<AnalyticsExpression> ungroupedExpressions) {
+ this.ungroupedReductionManager = ungroupedReductionManager;
+ this.ungroupedData = ungroupedReductionManager.newDataCollection();
+ this.ungroupedReductionManager.addLastingCollectTarget(ungroupedData);
+
+ this.ungroupedExpressions = ungroupedExpressions;
+ this.ungroupedExpressionCalculator = new ExpressionCalculator(ungroupedExpressions);
+ this.groupingManagers = new HashMap<>();
+ }
+
+ /**
+ * Get the collection manager for ungrouped expressions, including grouped expressions if
+ * the old request notation is used.
+ *
+ * @return the collection manager for the ungrouped expressions
+ */
+ public ReductionCollectionManager getUngroupedCollectionManager() {
+ return ungroupedReductionManager;
+ }
+
+ /**
+ * Get the collection manager for all ungrouped expressions, including grouped expressions if
+ * the old request notation is used.
+ *
+ * @return the collection manager for the ungrouped expressions
+ */
+ public ReductionDataCollection getUngroupedData() {
+ return ungroupedData;
+ }
+
+ /**
+ * Return all ungrouped expressions, including grouped expressions if
+ * the old request notation is used.
+ *
+ * @return an {@link Iterable} of the ungrouped expressions
+ */
+ public Iterable<AnalyticsExpression> getUngroupedExpressions() {
+ return ungroupedExpressions;
+ }
+
+ /**
+ * Generate the results of all ungrouped expressions, including grouped expressions if
+ * the old request notation is used.
+ *
+ * @param response the response to add the ungrouped results to.
+ */
+ public void addUngroupedResults(Map<String,Object> response) {
+ ungroupedReductionManager.setData(ungroupedData);
+ ungroupedExpressionCalculator.addResults(response);
+ }
+
+ /**
+ * Generate the results of all ungrouped expressions, including grouped expressions if
+ * the old request notation is used.
+ *
+ * @return the map containing the ungrouped results
+ */
+ public Map<String,Object> getUngroupedResults() {
+ ungroupedReductionManager.setData(ungroupedData);
+ return ungroupedExpressionCalculator.getResults();
+ }
+
+ /**
+ * Add a grouping to the request.
+ *
+ * @param groupingManager that manages the grouping
+ */
+ public void addGrouping(AnalyticsGroupingManager groupingManager) {
+ groupingManagers.put(groupingManager.getName(), groupingManager);
+ }
+
+ /**
+ * Import the shard data for this request from a bit-stream,
+ * exported by the {@link #exportShardData} method in the each of the collection's shards.
+ * <p>
+ * First the overall data is imported, then the grouping data is imported.
+ *
+ * @param input The bit-stream to import the shard data from
+ * @throws IOException if an exception occurs while reading from the {@link DataInput}
+ */
+ public synchronized void importShardData(DataInput input) throws IOException {
+ ungroupedReductionManager.setShardInput(input);
+
+ // The ungroupedData will not exist for the first shard imported
+ if (ungroupedData == null) {
+ ungroupedData = ungroupedReductionManager.newDataCollectionIO();
+ } else {
+ ungroupedReductionManager.prepareReductionDataIO(ungroupedData);
+ }
+ ungroupedReductionManager.mergeData();
+
+ int size = input.readInt();
+ while (--size >= 0) {
+ String groupingName = input.readUTF();
+ groupingManagers.get(groupingName).importShardData(input);
+ }
+ }
+
+ /**
+ * Export the shard data for this request through a bit-stream,
+ * to be imported by the {@link #importShardData} method in the originating shard.
+ * <p>
+ * First the overall data is exported, then the grouping data is exported.
+ *
+ * @param output The bit-stream to output the shard data through
+ * @throws IOException if an exception occurs while writing to the {@link DataOutput}
+ */
+ public void exportShardData(DataOutput output) throws IOException {
+ ungroupedReductionManager.setShardOutput(output);
+
+ ungroupedReductionManager.prepareReductionDataIO(ungroupedData);
+ ungroupedReductionManager.exportData();
+
+ output.writeInt(groupingManagers.size());
+ for (String groupingName : groupingManagers.keySet()) {
+ output.writeUTF(groupingName);
+ groupingManagers.get(groupingName).exportShardData(output);
+ }
+ }
+
+ /**
+ * Consolidate the information of all {@link StreamingFacet}s contained within the request, since
+ * they need to be collected along with the overall results during the streaming phase of the
+ * {@link AnalyticsDriver}.
+ *
+ * @return the info for all {@link StreamingFacet}s
+ */
+ public StreamingInfo getStreamingFacetInfo() {
+ StreamingInfo streamingInfo = new StreamingInfo();
+ ArrayList<ReductionCollectionManager> groupingCollectors = new ArrayList<>();
+ groupingManagers.values().forEach( grouping -> {
+ // If a grouping has streaming facets, then that groupings expressions
+ // must be collected during the streaming phase.
+ if (grouping.getStreamingFacets( facet -> streamingInfo.streamingFacets.add(facet) )) {
+ groupingCollectors.add(grouping.getReductionManager());
+ }
+ });
+
+ // Create an streaming collection manager to manage the collection of all ungrouped expressions and
+ // grouped expressions that are calculated over streaming facets.
+ streamingInfo.streamingCollectionManager = ungroupedReductionManager.merge(groupingCollectors);
+ return streamingInfo;
+ }
+
+ /**
+ * Class to encapsulate all necessary data for collecting {@link StreamingFacet}s.
+ */
+ public static class StreamingInfo {
+ Collection<StreamingFacet> streamingFacets = new ArrayList<>();
+ /**
+ * Manages the collection of all expressions needed for streaming facets
+ */
+ ReductionCollectionManager streamingCollectionManager;
+ }
+
+ /**
+ * Create the {@link FacetValueQueryExecuter}s for all {@link AbstractSolrQueryFacet}s contained in the request.
+ *
+ * @param filter representing the overall search query
+ * @param queryRequest of the overall search query
+ * @return an {@link Iterable} of executers
+ */
+ public Iterable<FacetValueQueryExecuter> getFacetExecuters(Filter filter, SolrQueryRequest queryRequest) {
+ ArrayList<FacetValueQueryExecuter> facetExecutors = new ArrayList<>();
+ groupingManagers.values().forEach( grouping -> {
+ grouping.getFacetExecuters(filter, queryRequest, executor -> facetExecutors.add(executor));
+ });
+ return facetExecutors;
+ }
+
+ /**
+ * Create the response for a request given in the old olap-style format.
+ * The old response returned overall expressions within groupings.
+ *
+ * @return a {@link NamedList} representation of the response
+ */
+ public NamedList<Object> createOldResponse() {
+ NamedList<Object> analyticsResponse = new NamedList<>();
+ Map<String,Object> ungroupedResults = getUngroupedResults();
+ groupingManagers.forEach( (name, groupingManager) -> {
+ analyticsResponse.add(name, groupingManager.createOldResponse(ungroupedResults));
+ });
+
+ return analyticsResponse;
+ }
+
+ /**
+ * Create the response for a request.
+ *
+ * <p>
+ * NOTE: Analytics requests specified in the old olap-style format
+ * have their responses generated by {@link #createOldResponse()}.
+ *
+ * @return a {@link Map} representation of the response
+ */
+ public Map<String,Object> createResponse() {
+ Map<String,Object> analyticsResponse = new HashMap<>();
+ if (ungroupedExpressions.size() > 0) {
+ addUngroupedResults(analyticsResponse);
+ }
+
+ Map<String,Object> groupingsResponse = new HashMap<>();
+ groupingManagers.forEach( (name, groupingManager) -> {
+ groupingsResponse.put(name, groupingManager.createResponse());
+ });
+
+ if (groupingsResponse.size() > 0) {
+ analyticsResponse.put(AnalyticsResponseHeadings.GROUPINGS, groupingsResponse);
+ }
+ return analyticsResponse;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/d5963beb/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestParser.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestParser.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestParser.java
new file mode 100644
index 0000000..bfa62e2
--- /dev/null
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/AnalyticsRequestParser.java
@@ -0,0 +1,549 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+import org.apache.solr.analytics.facet.PivotFacet;
+import org.apache.solr.analytics.facet.PivotNode;
+import org.apache.solr.analytics.facet.QueryFacet;
+import org.apache.solr.analytics.facet.RangeFacet;
+import org.apache.solr.analytics.facet.ValueFacet;
+import org.apache.solr.analytics.facet.SortableFacet.FacetSortSpecification;
+import org.apache.solr.analytics.facet.compare.DelegatingComparator;
+import org.apache.solr.analytics.facet.compare.FacetValueComparator;
+import org.apache.solr.analytics.facet.compare.FacetResultsComparator;
+import org.apache.solr.analytics.value.AnalyticsValue;
+import org.apache.solr.analytics.value.AnalyticsValueStream;
+import org.apache.solr.analytics.value.ComparableValue;
+import org.apache.solr.analytics.value.StringValueStream;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.params.FacetParams.FacetRangeInclude;
+import org.apache.solr.common.params.FacetParams.FacetRangeOther;
+import org.apache.solr.schema.IndexSchema;
+import org.apache.solr.schema.SchemaField;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Class to manage the parsing of new-style analytics requests.
+ */
+public class AnalyticsRequestParser {
+
+ private static ObjectMapper mapper = new ObjectMapper();
+
+ public static void init() {
+ mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
+ mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true);
+ }
+
+ public static final String analyticsParamName = "analytics";
+
+ private static Predicate<String> request = acceptNames("request", "req");
+ private static Predicate<String> functions = acceptNames("functions", "funcs", "f");
+ private static Predicate<String> expressions = acceptNames("expressions", "exprs", "e");
+
+ private static Predicate<String> grouping = acceptNames("grouping", "group", "g");
+
+ private static Predicate<String> valueFacet = acceptNames("valuefacet", "vf");
+
+ private static Predicate<String> pivotFacet = acceptNames("pivotfacet", "pf");
+ private static Predicate<String> pivot = acceptNames("pivot", "p");
+
+ private static Predicate<String> sort = acceptNames("sort", "s");
+ private static Predicate<String> sortExpression = acceptNames("expression", "expr");
+ private static Predicate<String> sortFacetValue = acceptNames("facetvalue", "fv");
+ private static Predicate<String> sortDirection = acceptNames("direction", "dir");
+ private static Predicate<String> sortAscending = acceptNames("ascending", "asc", "a");
+ private static Predicate<String> sortDescending = acceptNames("descending", "desc", "d");
+ private static Predicate<String> sortLimit = acceptNames("limit", "l");
+ private static Predicate<String> sortOffset = acceptNames("offset", "o");
+
+ private static Predicate<String> rangeFacet = acceptNames("rangefacet", "rf");
+ private static Predicate<String> rangeGaps = acceptNames("gaps", "g");
+ private static Predicate<String> rangeHardEnd = acceptNames("hardend", "he");
+ private static Predicate<String> rangeInclude = acceptNames("include", "i");
+ private static Predicate<String> rangeOthers = acceptNames("others", "o");
+
+ private static Predicate<String> queryFacet = acceptNames("queryfacet", "qf");
+ private static Predicate<String> query = acceptNames("query", "q");
+
+ private static Predicate<String> acceptNames(String... names) {
+ return Pattern.compile("^(?:" + Arrays.stream(names).reduce((a,b) -> a + "|" + b).orElse("") + ")$", Pattern.CASE_INSENSITIVE).asPredicate();
+ }
+
+ // Defaults
+ public static final String DEFAULT_SORT_DIRECTION = "ascending";
+ public static final int DEFAULT_OFFSET = 0;
+ public static final int DEFAULT_LIMIT = -1;
+ public static final boolean DEFAULT_HARDEND = false;
+
+ @JsonInclude(Include.NON_EMPTY)
+ public static class AnalyticsRequest {
+ public Map<String, String> functions;
+ public Map<String, String> expressions;
+
+ public Map<String, AnalyticsGroupingRequest> groupings;
+ }
+
+ public static class AnalyticsGroupingRequest {
+ public Map<String, String> expressions;
+
+ public Map<String, AnalyticsFacetRequest> facets;
+ }
+
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.PROPERTY,
+ property = "type"
+ )
+ @JsonSubTypes({
+ @Type(value = AnalyticsValueFacetRequest.class, name = "value"),
+ @Type(value = AnalyticsPivotFacetRequest.class, name = "pivot"),
+ @Type(value = AnalyticsRangeFacetRequest.class, name = "range"),
+ @Type(value = AnalyticsQueryFacetRequest.class, name = "query") }
+ )
+ @JsonInclude(Include.NON_EMPTY)
+ public static interface AnalyticsFacetRequest { }
+
+ @JsonTypeName("value")
+ public static class AnalyticsValueFacetRequest implements AnalyticsFacetRequest {
+ public String expression;
+ public AnalyticsSortRequest sort;
+ }
+
+ @JsonTypeName("pivot")
+ public static class AnalyticsPivotFacetRequest implements AnalyticsFacetRequest {
+ public List<AnalyticsPivotRequest> pivots;
+ }
+
+ public static class AnalyticsPivotRequest {
+ public String name;
+ public String expression;
+ public AnalyticsSortRequest sort;
+ }
+
+ @JsonInclude(Include.NON_EMPTY)
+ public static class AnalyticsSortRequest {
+ public List<AnalyticsSortCriteriaRequest> criteria;
+ public int limit = DEFAULT_LIMIT;
+ public int offset = DEFAULT_OFFSET;
+ }
+
+ @JsonTypeInfo(
+ use = JsonTypeInfo.Id.NAME,
+ include = JsonTypeInfo.As.PROPERTY,
+ property = "type"
+ )
+ @JsonSubTypes({
+ @Type(value = AnalyticsExpressionSortRequest.class, name = "expression"),
+ @Type(value = AnalyticsFacetValueSortRequest.class, name = "facetvalue") }
+ )
+ @JsonInclude(Include.NON_EMPTY)
+ public static abstract class AnalyticsSortCriteriaRequest {
+ public String direction;
+ }
+
+ @JsonTypeName("expression")
+ public static class AnalyticsExpressionSortRequest extends AnalyticsSortCriteriaRequest {
+ public String expression;
+ }
+
+ @JsonTypeName("facetvalue")
+ public static class AnalyticsFacetValueSortRequest extends AnalyticsSortCriteriaRequest { }
+
+ @JsonTypeName("range")
+ public static class AnalyticsRangeFacetRequest implements AnalyticsFacetRequest {
+ public String field;
+ public String start;
+ public String end;
+ public List<String> gaps;
+ public boolean hardend = DEFAULT_HARDEND;
+ public List<String> include;
+ public List<String> others;
+ }
+
+ @JsonTypeName("query")
+ public static class AnalyticsQueryFacetRequest implements AnalyticsFacetRequest {
+ public Map<String, String> queries;
+ }
+
+ /* ***************
+ * Request & Groupings
+ * ***************/
+
+ public static AnalyticsRequestManager parse(AnalyticsRequest request, ExpressionFactory expressionFactory, boolean isDistribRequest) throws SolrException {
+ AnalyticsRequestManager manager = constructRequest(request, expressionFactory, isDistribRequest);
+ if (isDistribRequest) {
+ try {
+ manager.analyticsRequest = mapper.writeValueAsString(request);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return manager;
+ }
+
+ public static AnalyticsRequestManager parse(String rawRequest, ExpressionFactory expressionFactory, boolean isDistribRequest) throws SolrException {
+ JsonParser parser;
+ try {
+ parser = new JsonFactory().createParser(rawRequest)
+ .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
+ .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
+ } catch (IOException e1) {
+ throw new RuntimeException(e1);
+ }
+ AnalyticsRequest request;
+ try {
+ request = mapper.readValue(parser, AnalyticsRequest.class);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ AnalyticsRequestManager manager = constructRequest(request, expressionFactory, isDistribRequest);
+ if (isDistribRequest) {
+ manager.analyticsRequest = rawRequest;
+ }
+ return manager;
+ }
+
+ private static AnalyticsRequestManager constructRequest(AnalyticsRequest request, ExpressionFactory expressionFactory, boolean isDistribRequest) throws SolrException {
+ expressionFactory.startRequest();
+
+ // Functions
+ if (request.functions != null) {
+ request.functions.forEach( (funcSig, retSig) -> expressionFactory.addUserDefinedVariableFunction(funcSig, retSig));
+ }
+
+ // Expressions
+ Map<String,AnalyticsExpression> topLevelExpressions;
+ if (request.expressions != null) {
+ topLevelExpressions = constructExpressions(request.expressions, expressionFactory);
+ } else {
+ topLevelExpressions = new HashMap<>();
+ }
+ AnalyticsRequestManager manager = new AnalyticsRequestManager(expressionFactory.createReductionManager(isDistribRequest), topLevelExpressions.values());
+
+ // Groupings
+ if (request.groupings != null) {
+ request.groupings.forEach( (name, grouping) -> {
+ manager.addGrouping(constructGrouping(name, grouping, expressionFactory, isDistribRequest));
+ });
+ }
+ return manager;
+ }
+
+ private static AnalyticsGroupingManager constructGrouping(String name, AnalyticsGroupingRequest grouping, ExpressionFactory expressionFactory, boolean isDistribRequest) throws SolrException {
+ expressionFactory.startGrouping();
+
+ // Expressions
+ if (grouping.expressions == null || grouping.expressions.size() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Groupings must contain at least one expression, '" + name + "' has none.");
+ }
+
+ Map<String,AnalyticsExpression> expressions = constructExpressions(grouping.expressions, expressionFactory);
+ AnalyticsGroupingManager manager = new AnalyticsGroupingManager(name,
+ expressionFactory.createGroupingReductionManager(isDistribRequest),
+ expressions.values());
+
+ if (grouping.facets == null) {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Groupings must contain at least one facet, '" + name + "' has none.");
+ }
+ // Parse the facets
+ grouping.facets.forEach( (facetName, facet) -> {
+ if (facet instanceof AnalyticsValueFacetRequest) {
+ manager.addFacet(constructValueFacet(facetName, (AnalyticsValueFacetRequest) facet, expressionFactory, expressions));
+ } else if (facet instanceof AnalyticsPivotFacetRequest) {
+ manager.addFacet(constructPivotFacet(facetName, (AnalyticsPivotFacetRequest) facet, expressionFactory, expressions));
+ } else if (facet instanceof AnalyticsRangeFacetRequest) {
+ manager.addFacet(constructRangeFacet(facetName, (AnalyticsRangeFacetRequest) facet, expressionFactory.getSchema()));
+ } else if (facet instanceof AnalyticsQueryFacetRequest) {
+ manager.addFacet(constructQueryFacet(facetName, (AnalyticsQueryFacetRequest) facet));
+ } else {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"The facet type, '" + facet.getClass().toString() + "' in "
+ + "grouping '" + name + "' is not a valid type of facet");
+ }
+ });
+
+ return manager;
+ }
+
+ /* ***************
+ * Expression & Functions
+ * ***************/
+
+ private static Map<String, AnalyticsExpression> constructExpressions(Map<String, String> rawExpressions, ExpressionFactory expressionFactory) throws SolrException {
+ Map<String, AnalyticsExpression> expressions = new HashMap<>();
+ rawExpressions.forEach( (name, expression) -> {
+ AnalyticsValueStream exprVal = expressionFactory.createExpression(expression);
+ if (exprVal instanceof AnalyticsValue) {
+ if (exprVal.getExpressionType().isReduced()) {
+ expressions.put(name, (new AnalyticsExpression(name, (AnalyticsValue)exprVal)));
+ } else {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Top-level expressions must be reduced, the '" + name + "' expression is not.");
+ }
+ } else {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Top-level expressions must be single-valued, the '" + name + "' expression is not.");
+ }
+ });
+ return expressions;
+ }
+
+ /* ***************
+ * FACETS
+ * ***************/
+
+ /*
+ * Value Facets
+ */
+
+ private static ValueFacet constructValueFacet(String name, AnalyticsValueFacetRequest facetRequest, ExpressionFactory expressionFactory, Map<String, AnalyticsExpression> expressions) throws SolrException {
+ if (facetRequest.expression == null) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Value Facets must contain a mapping expression to facet over, '" + name + "' has none.");
+ }
+
+ // The second parameter must be a mapping expression
+ AnalyticsValueStream expr = expressionFactory.createExpression(facetRequest.expression);
+ if (!expr.getExpressionType().isUnreduced()) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Value Facet expressions must be mapping expressions, "
+ + "the following expression in value facet '" + name + "' contains a reduction: " + facetRequest.expression);
+ }
+ if (!(expr instanceof StringValueStream)) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Value Facet expressions must be castable to string expressions, "
+ + "the following expression in value facet '" + name + "' is not: " + facetRequest.expression);
+ }
+
+ ValueFacet facet = new ValueFacet(name, (StringValueStream)expr);
+
+ // Check if the value facet is sorted
+ if (facetRequest.sort != null) {
+ facet.setSort(constructSort(facetRequest.sort, expressions));
+ }
+ return facet;
+ }
+
+ /*
+ * Pivot Facets
+ */
+
+ private static PivotFacet constructPivotFacet(String name, AnalyticsPivotFacetRequest facetRequest, ExpressionFactory expressionFactory, Map<String, AnalyticsExpression> expressions) throws SolrException {
+ PivotNode<?> topPivot = null;
+
+ // Pivots
+ if (facetRequest.pivots == null || facetRequest.pivots.size() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Pivot Facets must contain at least one pivot to facet over, '" + name + "' has none.");
+ }
+
+ ListIterator<AnalyticsPivotRequest> iter = facetRequest.pivots.listIterator(facetRequest.pivots.size());
+ while (iter.hasPrevious()) {
+ topPivot = constructPivot(iter.previous(), topPivot, expressionFactory, expressions);
+ }
+
+ return new PivotFacet(name, topPivot);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static PivotNode<?> constructPivot(AnalyticsPivotRequest pivotRequest,
+ PivotNode<?> childPivot,
+ ExpressionFactory expressionFactory,
+ Map<String, AnalyticsExpression> expressions) throws SolrException {
+ if (pivotRequest.name == null || pivotRequest.name.length() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Pivots must have a name.");
+ }
+ if (pivotRequest.expression == null) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Pivots must have an expression to facet over, '" + pivotRequest.name + "' does not.");
+ }
+
+ // The second parameter must be a mapping expression
+ AnalyticsValueStream expr = expressionFactory.createExpression(pivotRequest.expression);
+ if (!expr.getExpressionType().isUnreduced()) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Pivot expressions must be mapping expressions, "
+ + "the following expression in pivot '" + pivotRequest.name + "' contains a reduction: " + pivotRequest.expression);
+ }
+ if (!(expr instanceof StringValueStream)) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Pivot expressions must be castable to string expressions, "
+ + "the following expression in pivot '" + pivotRequest.name + "' is not: '" + pivotRequest.expression);
+ }
+
+ PivotNode<?> pivot;
+ if (childPivot == null) {
+ pivot = new PivotNode.PivotLeaf(pivotRequest.name, (StringValueStream)expr);
+ } else {
+ pivot = new PivotNode.PivotBranch(pivotRequest.name, (StringValueStream)expr, childPivot);
+ }
+
+ // Check if the pivot is sorted
+ if (pivotRequest.sort != null) {
+ pivot.setSort(constructSort(pivotRequest.sort, expressions));
+ }
+ return pivot;
+ }
+
+ /*
+ * Range Facets
+ */
+
+ private static RangeFacet constructRangeFacet(String name, AnalyticsRangeFacetRequest facetRequest, IndexSchema schema) throws SolrException {
+ if (facetRequest.field == null || facetRequest.field.length() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must specify a field to facet over, '" +name + "' does not.");
+ }
+ SchemaField field = schema.getFieldOrNull(facetRequest.field);
+ if (field == null) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must have a valid field as the second parameter. The '" + name + "' facet "
+ + "tries to facet over the non-existent field: " + facetRequest.field);
+ }
+
+ if (facetRequest.start == null || facetRequest.start.length() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must specify a start value, '" +name + "' does not.");
+ }
+ if (facetRequest.end == null || facetRequest.end.length() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must specify a end value, '" +name + "' does not.");
+ }
+ if (facetRequest.gaps == null || facetRequest.gaps.size() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Range Facets must specify a gap or list of gaps to determine facet buckets, '" +name + "' does not.");
+ }
+ RangeFacet facet = new RangeFacet(name, field, facetRequest.start, facetRequest.end, facetRequest.gaps);
+
+ facet.setHardEnd(facetRequest.hardend);
+
+ if (facetRequest.include != null && facetRequest.include.size() > 0) {
+ facet.setInclude(constructInclude(facetRequest.include));
+ }
+ if (facetRequest.others != null && facetRequest.others.size() > 0) {
+ facet.setOthers(constructOthers(facetRequest.others, name));
+ }
+ return facet;
+ }
+
+ private static EnumSet<FacetRangeInclude> constructInclude(List<String> includes) throws SolrException {
+ return FacetRangeInclude.parseParam(includes.toArray(new String[includes.size()]));
+ }
+
+ private static EnumSet<FacetRangeOther> constructOthers(List<String> othersRequest, String facetName) throws SolrException {
+ EnumSet<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
+ for (String rawOther : othersRequest) {
+ if (!others.add(FacetRangeOther.get(rawOther))) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Duplicate include value '" + rawOther + "' found in range facet '" + facetName + "'");
+ }
+ }
+ if (others.contains(FacetRangeOther.NONE)) {
+ if (others.size() > 1) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Include value 'NONE' is used with other includes in a range facet '" + facetName + "'. "
+ + "If 'NONE' is used, it must be the only include.");
+ }
+ return EnumSet.noneOf(FacetRangeOther.class);
+ }
+ if (others.contains(FacetRangeOther.ALL)) {
+ if (others.size() > 1) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Include value 'ALL' is used with other includes in a range facet '" + facetName + "'. "
+ + "If 'ALL' is used, it must be the only include.");
+ }
+ return EnumSet.of(FacetRangeOther.BEFORE, FacetRangeOther.BETWEEN, FacetRangeOther.AFTER);
+ }
+ return others;
+ }
+
+ /*
+ * Query Facets
+ */
+
+ private static QueryFacet constructQueryFacet(String name, AnalyticsQueryFacetRequest facetRequest) throws SolrException {
+ if (facetRequest.queries == null || facetRequest.queries.size() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Query Facets must be contain at least 1 query to facet over, '" + name + "' does not.");
+ }
+
+ // The first param must be the facet name
+ return new QueryFacet(name, facetRequest.queries);
+ }
+
+ /*
+ * Facet Sorting
+ */
+
+ private static FacetSortSpecification constructSort(AnalyticsSortRequest sortRequest, Map<String, AnalyticsExpression> expressions) throws SolrException {
+ if (sortRequest.criteria == null || sortRequest.criteria.size() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Sorts must be given at least 1 criteria.");
+ }
+
+ return new FacetSortSpecification(constructSortCriteria(sortRequest.criteria, expressions), sortRequest.limit, sortRequest.offset);
+ }
+
+ private static FacetResultsComparator constructSortCriteria(List<AnalyticsSortCriteriaRequest> criteria, Map<String, AnalyticsExpression> expressions) {
+ ArrayList<FacetResultsComparator> comparators = new ArrayList<>();
+ for (AnalyticsSortCriteriaRequest criterion : criteria) {
+ FacetResultsComparator comparator;
+ if (criterion instanceof AnalyticsExpressionSortRequest) {
+ comparator = constructExpressionSortCriteria((AnalyticsExpressionSortRequest) criterion, expressions);
+ } else if (criterion instanceof AnalyticsFacetValueSortRequest) {
+ comparator = constructFacetValueSortCriteria((AnalyticsFacetValueSortRequest) criterion);
+ } else {
+ // Shouldn't happen
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Sort Criteria must either be expressions or facetValues, '" + criterion.getClass().getName() + "' given.");
+ }
+ if (criterion.direction != null && criterion.direction.length() > 0) {
+ if (sortAscending.test(criterion.direction)) {
+ comparator.setDirection(true);
+ } else if (sortDescending.test(criterion.direction)) {
+ comparator.setDirection(false);
+ } else {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Sort direction '" + criterion.direction + " is not a recognized direction.");
+ }
+ }
+ comparators.add(comparator);
+ }
+ return DelegatingComparator.joinComparators(comparators);
+ }
+
+ private static FacetResultsComparator constructExpressionSortCriteria(AnalyticsExpressionSortRequest criterion, Map<String, AnalyticsExpression> expressions) {
+ if (criterion.expression == null || criterion.expression.length() == 0) {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Expression Sorts must contain an expression parameter, none given.");
+ }
+
+ AnalyticsExpression expression = expressions.get(criterion.expression);
+ if (expression == null) {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Sort Expression not defined within the grouping: " + criterion.expression);
+ }
+ if (!(expression.getExpression() instanceof ComparableValue)) {
+ throw new SolrException(ErrorCode.BAD_REQUEST,"Expression Sorts must be comparable, the following is not: " + criterion.expression);
+ }
+ return ((ComparableValue)expression.getExpression()).getObjectComparator(expression.getName());
+ }
+
+ private static FacetResultsComparator constructFacetValueSortCriteria(AnalyticsFacetValueSortRequest criterion) {
+ return new FacetValueComparator();
+ }
+}