diff --git a/e2e-test/pom.xml b/e2e-test/pom.xml
index 2faf7684a..eb27c4090 100644
--- a/e2e-test/pom.xml
+++ b/e2e-test/pom.xml
@@ -17,6 +17,7 @@
src/test/resources/SearchSuite.xml
+ 23.0
2.9.10.8
community
@@ -79,6 +80,10 @@
+
+ com.google.guava
+ guava
+
org.alfresco
alfresco-governance-services-automation-enterprise-rest-api
@@ -94,7 +99,7 @@
com.fasterxml.jackson.core
jackson-databind
- ${jackson.databind.version}
+ ${dependency.jackson.version}
org.alfresco
diff --git a/pom.xml b/pom.xml
index a30632ebf..012d0a8d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
alfresco-search-and-insight-parent
2.0.6-SNAPSHOT
pom
- Alfresco Search And Insight Parent
+ Alfresco Search And Insight Engine
alfresco-enterprise-releases
@@ -53,73 +53,207 @@
enterprise
true
false
+
+ 1.3
+ 13.4
+
+ 2.13.4
+ 2.13.4.2
+
+ 31.1-jre
1.21
+ 3.11
+ 2.6
3.0.1
- 1.27
- 2.7.7
2.3.12
- 2.0.6.1
- 5.3.18
4.5.13
1.9.14-atlassian-6
+ 2.7.1
+
+ 1.1.4c
+ 1.2.0
+ 2.3.3
+
+ 1.32.0
+ 1.7.36
+ 3.4.8
+
+ 3.1.0
+
+ 4.13.2
+ 4.6.1
+ 2.7.8
+ 1.1.0
+ 3.19.6
+ 2.7.0
+ 3.1.8
+
+ org.codehaus.janino
+ commons-compiler
+ ${dependency.janino.version}
+
+
+ org.codehaus.janino
+ janino
+ ${dependency.janino.version}
+
+
+ com.jayway.jsonpath
+ json-path
+ ${dependency.jayway.jsonpath.version}
+
+
+ com.google.protobuf
+ protobuf-java
+ ${dependency.protobuf.version}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ ${dependency.jackson.version}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ ${dependency.jackson.version}
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${dependency.jackson-databind.version}
+
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-smile
+ ${dependency.jackson.version}
+
+
+ javax.servlet
+ javax.servlet-api
+ ${dependency.javax.servlet.api.version}
+
+
+ com.carrotsearch.thirdparty
+ simple-xml-safe
+ ${dependency.carrotsearch.thirdpaty.simple-xml-safe.version}
+
+
+ org.apache.calcite
+ calcite-core
+ ${dependency.calcite.version}
+
+
+ com.sun.xml.bind
+ jaxb-xjc
+ ${dependency.jaxb-xjc.version}
+
+
+ jaxen
+ jaxen
+ ${dependency.jaxen.version}
+
+
+ xpp3
+ xpp3
+ ${dependency.xpp3.version}
+
+
+ com.google.guava
+ guava
+ ${dependency.google.guava.version}
+
+
+ org.alfresco
+ alfresco-xmlfactory
+ ${dependency.alfresco.xml-factory.version}
+
+
+ commons-lang
+ commons-lang
+ ${dependency.apache-commons-lang.version}
+
+
+ org.apache.cxf
+ cxf-core
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-bindings-soap
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-bindings-xml
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-databinding-jaxb
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-frontend-jaxws
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-frontend-simple
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-transports-http
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-ws-addr
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-ws-policy
+ ${dependency.cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-wsdl
+ ${dependency.cxf.version}
+
+
+ org.apache.commons
+ commons-lang3
+ ${dependency.apache-commons-lang3.version}
+
+
+ org.slf4j
+ slf4j-api
+ ${dependency.slf4j.version}
+
+
+ org.slf4j
+ slf4j-reload4j
+ ${dependency.slf4j.version}
+
org.apache.commons
commons-compress
${dependency.apache-commons-compress.version}
-
- org.apache.tika
- tika-core
- ${dependency.tika.version}
-
-
- org.apache.tika
- tika-java7
- ${dependency.tika.version}
-
-
- org.apache.tika
- tika-parsers
- ${dependency.tika.version}
-
-
- org.apache.tika
- tika-xmp
- ${dependency.tika.version}
-
jakarta.xml.bind
jakarta.xml.bind-api
${dependency.jakarta.xml.bind-api.version}
-
- com.adobe.xmp
- xmpcore
- 6.1.11
-
-
- org.apache.hadoop
- hadoop-annotations
- ${dependency.hadoop.version}
-
-
- org.apache.hadoop
- hadoop-auth
- ${dependency.hadoop.version}
-
-
- org.apache.hadoop
- hadoop-common
- ${dependency.hadoop.version}
-
-
- org.apache.hadoop
- hadoop-hdfs
- ${dependency.hadoop.version}
-
org.restlet.jee
org.restlet
@@ -151,6 +285,26 @@
jackson-mapper-asl
${dependency.codehaus.jackson.version}
+
+ junit
+ junit
+ ${dependency.junit.version}
+
+
+ org.mockito
+ mockito-core
+ ${dependency.mockito.version}
+
+
+ com.carrotsearch.randomizedtesting
+ randomizedtesting-runner
+ ${dependency.carrotsearch.randomizedtesting.version}
+
+
+ org.apache.chemistry.opencmis
+ chemistry-opencmis-client-impl
+ ${dependency.chemistry.opencmis.version}
+
diff --git a/search-services/alfresco-search/pom.xml b/search-services/alfresco-search/pom.xml
index 874830fbc..9b036bca1 100644
--- a/search-services/alfresco-search/pom.xml
+++ b/search-services/alfresco-search/pom.xml
@@ -42,6 +42,38 @@
${solr.version}
provided
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-smile
+
+
+ org.codehaus.janino
+ *
+
+
+ org.apache.calcite
+ *
+
+
+ org.apache.calcite.avatica
+ *
+
+
+ org.apache.hadoop
+ *
+
jdk.tools
jdk.tools
@@ -52,26 +84,92 @@
-
org.apache.solr
solr-analysis-extras
${solr.version}
provided
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-smile
+
+
+ org.codehaus.janino
+ *
+
+
+ org.apache.calcite
+ *
+
+
+ org.apache.calcite.avatica
+ *
+
+
+ org.apache.calcite.avatica
+ *
+
log4j
log4j
+
+ org.apache.hadoop
+ *
+
-
org.apache.solr
solr-langid
${solr.version}
provided
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-smile
+
+
+ org.codehaus.janino
+ *
+
+
+ com.adobe.xmp
+ *
+
+
+ org.apache.calcite
+ *
+
+
+ org.apache.calcite.avatica
+ *
+
xercesImpl
xerces
@@ -88,10 +186,18 @@
org.bouncycastle
bcprov-jdk15on
+
+ org.apache.hadoop
+ *
+
log4j
log4j
+
+ org.apache.tika
+ *
+
org.apache.poi
*
@@ -102,13 +208,44 @@
-
org.apache.solr
solr-clustering
${solr.version}
provided
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-smile
+
+
+ org.codehaus.janino
+ *
+
+
+ org.apache.calcite
+ *
+
+
+ org.apache.calcite.avatica
+ *
+
+
+ org.apache.hadoop
+ *
+
org.simpleframework
simple-xml
@@ -123,119 +260,103 @@
com.carrotsearch.thirdparty
simple-xml-safe
- 2.7.1
provided
-
org.slf4j
slf4j-api
- ${slf4j.version}
-
org.slf4j
slf4j-reload4j
- ${slf4j.version}
-
jaxen
jaxen
- 1.2.0
-
com.sun.xml.bind
jaxb-xjc
- 2.3.3
-
-
-
- org.apache.commons
- commons-lang3
- 3.11
org.apache.cxf
cxf-core
- ${cxf.version}
org.apache.cxf
cxf-rt-bindings-soap
- ${cxf.version}
org.apache.cxf
cxf-rt-bindings-xml
- ${cxf.version}
org.apache.cxf
cxf-rt-databinding-jaxb
- ${cxf.version}
org.apache.cxf
cxf-rt-frontend-jaxws
- ${cxf.version}
org.apache.cxf
cxf-rt-frontend-simple
- ${cxf.version}
org.apache.cxf
cxf-rt-transports-http
- ${cxf.version}
org.apache.cxf
cxf-rt-ws-addr
- ${cxf.version}
org.apache.cxf
cxf-rt-ws-policy
- ${cxf.version}
org.apache.cxf
cxf-rt-wsdl
- ${cxf.version}
xpp3
xpp3
- 1.1.4c
-
org.alfresco
alfresco-xmlfactory
- 1.3
-
-
-
- org.apache.calcite
- calcite-core
- 1.13.0
-
-
- org.apache.calcite
- calcite-linq4j
- 1.13.0
-
- org.apache.calcite.avatica
- avatica-core
- 1.13.0
+ com.google.guava
+ guava
+
+
+ org.apache.calcite
+ calcite-core
+
+
+ org.codehaus.janino
+ commons-compiler
+
+
+ org.codehaus.janino
+ janino
+
+
+ com.jayway.jsonpath
+ json-path
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ commons-lang
+ commons-lang
org.apache.httpcomponents
@@ -246,14 +367,12 @@
junit
junit
- 4.13
test
org.mockito
mockito-core
- 4.6.1
test
@@ -263,6 +382,38 @@
${solr.version}
test
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-smile
+
+
+ org.codehaus.janino
+ *
+
+
+ org.apache.calcite
+ *
+
+
+ org.apache.calcite.avatica
+ *
+
+
+ org.apache.hadoop
+ *
+
log4j
log4j
@@ -272,13 +423,11 @@
com.carrotsearch.randomizedtesting
randomizedtesting-runner
- 2.7.8
test
org.apache.chemistry.opencmis
chemistry-opencmis-client-impl
- 1.1.0
test
diff --git a/search-services/alfresco-search/src/main/java/org/apache/solr/handler/component/HighlightComponent.java b/search-services/alfresco-search/src/main/java/org/apache/solr/handler/component/HighlightComponent.java
new file mode 100644
index 000000000..976a1d039
--- /dev/null
+++ b/search-services/alfresco-search/src/main/java/org/apache/solr/handler/component/HighlightComponent.java
@@ -0,0 +1,297 @@
+/*
+ * #%L
+ * Alfresco Search Services
+ * %%
+ * Copyright (C) 2005 - 2020 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.apache.solr.handler.component;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import com.google.common.base.Objects;
+import org.apache.lucene.search.Query;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.HighlightParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.highlight.DefaultSolrHighlighter;
+import org.apache.solr.highlight.PostingsSolrHighlighter;
+import org.apache.solr.highlight.SolrHighlighter;
+import org.apache.solr.highlight.UnifiedSolrHighlighter;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.search.QParser;
+import org.apache.solr.search.QParserPlugin;
+import org.apache.solr.search.QueryParsing;
+import org.apache.solr.search.SyntaxError;
+import org.apache.solr.util.SolrPluginUtils;
+import org.apache.solr.util.plugin.PluginInfoInitialized;
+import org.apache.solr.util.plugin.SolrCoreAware;
+
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toMap;
+
+/**
+ * TODO!
+ *
+ *
+ * @since solr 1.3
+ */
+public class HighlightComponent extends SearchComponent implements PluginInfoInitialized, SolrCoreAware
+{
+ public enum HighlightMethod {
+ UNIFIED("unified"),
+ FAST_VECTOR("fastVector"),
+ POSTINGS("postings"),
+ ORIGINAL("original");
+
+ private static final Map METHODS = Collections.unmodifiableMap(Stream.of(values())
+ .collect(toMap(HighlightMethod::getMethodName, Function.identity())));
+
+ private final String methodName;
+
+ HighlightMethod(String method) {
+ this.methodName = method;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public static HighlightMethod parse(String method) {
+ return METHODS.get(method);
+ }
+ }
+
+ public static final String COMPONENT_NAME = "highlight";
+
+ private PluginInfo info = PluginInfo.EMPTY_INFO;
+
+ @Deprecated // DWS: in 7.0 lets restructure the abstractions/relationships
+ private SolrHighlighter solrConfigHighlighter;
+
+ /**
+ * @deprecated instead depend on {@link #process(ResponseBuilder)} to choose the highlighter based on
+ * {@link HighlightParams#METHOD}
+ */
+ @Deprecated
+ public static SolrHighlighter getHighlighter(SolrCore core) {
+ HighlightComponent hl = (HighlightComponent) core.getSearchComponents().get(HighlightComponent.COMPONENT_NAME);
+ return hl==null ? null: hl.getHighlighter();
+ }
+
+ @Deprecated
+ public SolrHighlighter getHighlighter() {
+ return solrConfigHighlighter;
+ }
+
+ @Override
+ public void init(PluginInfo info) {
+ this.info = info;
+ }
+
+ @Override
+ public void prepare(ResponseBuilder rb) throws IOException {
+ SolrParams params = rb.req.getParams();
+ rb.doHighlights = solrConfigHighlighter.isHighlightingEnabled(params);
+ if(rb.doHighlights){
+ rb.setNeedDocList(true);
+ String hlq = params.get(HighlightParams.Q);
+ String hlparser =
+ ofNullable(params.get(HighlightParams.QPARSER))
+ .orElseGet( () -> params.get(QueryParsing.DEFTYPE, QParserPlugin.DEFAULT_QTYPE));
+ if(hlq != null){
+ try {
+ QParser parser = QParser.getParser(hlq, hlparser, rb.req);
+ rb.setHighlightQuery(parser.getHighlightQuery());
+ } catch (SyntaxError e) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void inform(SolrCore core) {
+ List children = info.getChildren("highlighting");
+ if(children.isEmpty()) {
+ PluginInfo pluginInfo = core.getSolrConfig().getPluginInfo(SolrHighlighter.class.getName()); //TODO deprecated configuration remove later
+ if (pluginInfo != null) {
+ solrConfigHighlighter = core.createInitInstance(pluginInfo, SolrHighlighter.class, null, DefaultSolrHighlighter.class.getName());
+ } else {
+ DefaultSolrHighlighter defHighlighter = new DefaultSolrHighlighter(core);
+ defHighlighter.init(PluginInfo.EMPTY_INFO);
+ solrConfigHighlighter = defHighlighter;
+ }
+ } else {
+ solrConfigHighlighter = core.createInitInstance(children.get(0),SolrHighlighter.class,null, DefaultSolrHighlighter.class.getName());
+ }
+
+ }
+
+ @Override
+ public void process(ResponseBuilder rb) throws IOException {
+
+ if (rb.doHighlights) {
+ SolrQueryRequest req = rb.req;
+ SolrParams params = req.getParams();
+
+ SolrHighlighter highlighter = getHighlighter(params);
+
+ String[] defaultHighlightFields; //TODO: get from builder by default?
+ if (rb.getQparser() != null) {
+ defaultHighlightFields = rb.getQparser().getDefaultHighlightFields();
+ } else {
+ defaultHighlightFields = params.getParams(CommonParams.DF);
+ }
+
+ Query highlightQuery = rb.getHighlightQuery();
+ if(highlightQuery==null) {
+ if (rb.getQparser() != null) {
+ try {
+ highlightQuery = rb.getQparser().getHighlightQuery();
+ rb.setHighlightQuery( highlightQuery );
+ } catch (Exception e) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
+ }
+ } else {
+ highlightQuery = rb.getQuery();
+ rb.setHighlightQuery( highlightQuery );
+ }
+ }
+
+ // No highlighting if there is no query -- consider q.alt=*:*
+ if( highlightQuery != null ) {
+ NamedList sumData = highlighter.doHighlighting(
+ rb.getResults().docList,
+ highlightQuery,
+ req, defaultHighlightFields );
+
+ if(sumData != null) {
+ // TODO ???? add this directly to the response?
+ rb.rsp.add("highlighting", sumData);
+ }
+ }
+ }
+ }
+
+ protected SolrHighlighter getHighlighter(SolrParams params) {
+ HighlightMethod method = HighlightMethod.parse(params.get(HighlightParams.METHOD));
+ if (method == null) {
+ return solrConfigHighlighter;
+ }
+
+ switch (method) {
+ case UNIFIED:
+ if (solrConfigHighlighter instanceof UnifiedSolrHighlighter) {
+ return solrConfigHighlighter;
+ }
+ return new UnifiedSolrHighlighter(); // TODO cache one?
+ case POSTINGS:
+ if (solrConfigHighlighter instanceof PostingsSolrHighlighter) {
+ return solrConfigHighlighter;
+ }
+ return new PostingsSolrHighlighter(); // TODO cache one?
+ case FAST_VECTOR: // fall-through
+ case ORIGINAL:
+ if (solrConfigHighlighter instanceof DefaultSolrHighlighter) {
+ return solrConfigHighlighter;
+ } else {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+ "In order to use " + HighlightParams.METHOD + "=" + method.getMethodName() + " the configured" +
+ " highlighter in solrconfig must be " + DefaultSolrHighlighter.class);
+ }
+ default: throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void modifyRequest(ResponseBuilder rb, SearchComponent who, ShardRequest sreq) {
+ if (!rb.doHighlights) return;
+
+ // Turn on highlighting only only when retrieving fields
+ if ((sreq.purpose & ShardRequest.PURPOSE_GET_FIELDS) != 0) {
+ sreq.purpose |= ShardRequest.PURPOSE_GET_HIGHLIGHTS;
+ // should already be true...
+ sreq.params.set(HighlightParams.HIGHLIGHT, "true");
+ } else {
+ sreq.params.set(HighlightParams.HIGHLIGHT, "false");
+ }
+ }
+
+ @Override
+ public void handleResponses(ResponseBuilder rb, ShardRequest sreq) {
+ }
+
+ @Override
+ public void finishStage(ResponseBuilder rb) {
+ if (rb.doHighlights && rb.stage == ResponseBuilder.STAGE_GET_FIELDS) {
+
+ NamedList.NamedListEntry[] arr = new NamedList.NamedListEntry[rb.resultIds.size()];
+
+ // TODO: make a generic routine to do automatic merging of id keyed data
+ for (ShardRequest sreq : rb.finished) {
+ if ((sreq.purpose & ShardRequest.PURPOSE_GET_HIGHLIGHTS) == 0) continue;
+ for (ShardResponse srsp : sreq.responses) {
+ if (srsp.getException() != null) {
+ // can't expect the highlight content if there was an exception for this request
+ // this should only happen when using shards.tolerant=true
+ continue;
+ }
+ NamedList hl = (NamedList)srsp.getSolrResponse().getResponse().get("highlighting");
+ SolrPluginUtils.copyNamedListIntoArrayByDocPosInResponse(hl, rb.resultIds, arr);
+ }
+ }
+
+ // remove nulls in case not all docs were able to be retrieved
+ rb.rsp.add("highlighting", SolrPluginUtils.removeNulls(arr, new SimpleOrderedMap<>()));
+ }
+ }
+
+ ////////////////////////////////////////////
+ /// SolrInfoMBean
+ ////////////////////////////////////////////
+
+ @Override
+ public String getDescription() {
+ return "Highlighting";
+ }
+
+ @Override
+ public Category getCategory() {
+ return Category.HIGHLIGHTER;
+ }
+
+ @Override
+ public URL[] getDocs() {
+ return null;
+ }
+}
diff --git a/search-services/alfresco-search/src/main/java/org/apache/solr/handler/component/StatsValuesFactory.java b/search-services/alfresco-search/src/main/java/org/apache/solr/handler/component/StatsValuesFactory.java
new file mode 100644
index 000000000..e82dedbea
--- /dev/null
+++ b/search-services/alfresco-search/src/main/java/org/apache/solr/handler/component/StatsValuesFactory.java
@@ -0,0 +1,935 @@
+/*
+ * #%L
+ * Alfresco Search Services
+ * %%
+ * Copyright (C) 2005 - 2020 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.apache.solr.handler.component;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.nio.ByteBuffer;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.queries.function.FunctionValues;
+import org.apache.lucene.queries.function.ValueSource;
+import org.apache.lucene.util.BytesRef;
+import org.apache.solr.common.EnumFieldValue;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.handler.component.StatsField.Stat;
+import org.apache.solr.schema.*;
+
+import com.tdunning.math.stats.AVLTreeDigest;
+import com.google.common.hash.HashFunction;
+
+import org.apache.solr.util.hll.HLL;
+import org.apache.solr.util.hll.HLLType;
+
+/**
+ * Factory class for creating instance of
+ * {@link org.apache.solr.handler.component.StatsValues}
+ */
+public class StatsValuesFactory {
+
+ /**
+ * Creates an instance of StatsValues which supports values from the specified
+ * {@link StatsField}
+ *
+ * @param statsField
+ * {@link StatsField} whose statistics will be created by the
+ * resulting {@link StatsValues}
+ * @return Instance of {@link StatsValues} that will create statistics from
+ * values from the specified {@link StatsField}
+ */
+ public static StatsValues createStatsValues(StatsField statsField) {
+
+ final SchemaField sf = statsField.getSchemaField();
+
+ if (null == sf) {
+ // function stats
+ return new NumericStatsValues(statsField);
+ }
+
+ final FieldType fieldType = sf.getType(); // TODO: allow FieldType to provide impl.
+
+ if (TrieDateField.class.isInstance(fieldType) || DatePointField.class.isInstance(fieldType)) {
+ DateStatsValues statsValues = new DateStatsValues(statsField);
+ if (sf.multiValued()) {
+ return new SortedDateStatsValues(statsValues, statsField);
+ }
+ return statsValues;
+ } else if (TrieField.class.isInstance(fieldType) || PointField.class.isInstance(fieldType)) {
+
+ NumericStatsValues statsValue = new NumericStatsValues(statsField);
+ if (sf.multiValued()) {
+ return new SortedNumericStatsValues(statsValue, statsField);
+ }
+ return statsValue;
+ } else if (StrField.class.isInstance(fieldType)) {
+ return new StringStatsValues(statsField);
+ } else if (sf.getType().getClass().equals(EnumField.class)) {
+ return new EnumStatsValues(statsField);
+ } else {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+ "Field type " + fieldType + " is not currently supported");
+ }
+ }
+}
+
+/**
+ * Abstract implementation of
+ * {@link org.apache.solr.handler.component.StatsValues} that provides the
+ * default behavior for most StatsValues implementations.
+ *
+ * There are very few requirements placed on what statistics concrete
+ * implementations should collect, with the only required statistics being the
+ * minimum and maximum values.
+ */
+abstract class AbstractStatsValues implements StatsValues {
+ private static final String FACETS = "facets";
+
+ /** Tracks all data about tthe stats we need to collect */
+ final protected StatsField statsField;
+
+ /** may be null if we are collecting stats directly from a function ValueSource */
+ final protected SchemaField sf;
+ /**
+ * may be null if we are collecting stats directly from a function ValueSource
+ */
+ final protected FieldType ft;
+
+ // final booleans from StatsField to allow better inlining & JIT optimizing
+ final protected boolean computeCount;
+ final protected boolean computeMissing;
+ final protected boolean computeCalcDistinct; // needed for either countDistinct or distinctValues
+ final protected boolean computeMin;
+ final protected boolean computeMax;
+ final protected boolean computeMinOrMax;
+ final protected boolean computeCardinality;
+
+ /**
+ * Either a function value source to collect from, or the ValueSource associated
+ * with a single valued field we are collecting from. Will be null until/unless
+ * {@link #setNextReader} is called at least once
+ */
+ private ValueSource valueSource;
+ /**
+ * Context to use when retrieving FunctionValues, will be null until/unless
+ * {@link #setNextReader} is called at least once
+ */
+ private Map vsContext;
+ /**
+ * Values to collect, will be null until/unless {@link #setNextReader} is
+ * called at least once
+ */
+ protected FunctionValues values;
+
+ protected T max;
+ protected T min;
+ protected long missing;
+ protected long count;
+ protected long countDistinct;
+ protected final Set distinctValues;
+
+ /**
+ * Hash function that must be used by implementations of {@link #hash}
+ */
+ protected final HashFunction hasher;
+ // if null, no HLL logic can be computed; not final because of "union" optimization (see below)
+ private HLL hll;
+
+ // facetField facetValue
+ protected Map> facets = new HashMap<>();
+
+ protected AbstractStatsValues(StatsField statsField) {
+ this.statsField = statsField;
+ this.computeCount = statsField.calculateStats(Stat.count);
+ this.computeMissing = statsField.calculateStats(Stat.missing);
+ this.computeCalcDistinct = statsField.calculateStats(Stat.countDistinct)
+ || statsField.calculateStats(Stat.distinctValues);
+ this.computeMin = statsField.calculateStats(Stat.min);
+ this.computeMax = statsField.calculateStats(Stat.max);
+ this.computeMinOrMax = computeMin || computeMax;
+
+ this.distinctValues = computeCalcDistinct ? new TreeSet<>() : null;
+
+ this.computeCardinality = statsField.calculateStats(Stat.cardinality);
+ if ( computeCardinality ) {
+
+ hasher = statsField.getHllOptions().getHasher();
+ hll = statsField.getHllOptions().newHLL();
+ assert null != hll : "Cardinality requires an HLL";
+ } else {
+ hll = null;
+ hasher = null;
+ }
+
+ // alternatively, we could refactor a common base class that doesn't know/care
+ // about either SchemaField or ValueSource - but then there would be a lot of
+ // duplicate code between "NumericSchemaFieldStatsValues" and
+ // "NumericValueSourceStatsValues" which would have diff parent classes
+ //
+ // part of the complexity here being that the StatsValues API serves two
+ // masters: collecting concrete Values from things like DocValuesStats and
+ // the distributed aggregation logic, but also collecting docIds which it
+ // then
+ // uses to go out and pull concreate values from the ValueSource
+ // (from a func, or single valued field)
+ if (null != statsField.getSchemaField()) {
+ assert null == statsField.getValueSource();
+ this.sf = statsField.getSchemaField();
+ this.ft = sf.getType();
+ } else {
+ assert null != statsField.getValueSource();
+ assert null == statsField.getSchemaField();
+ this.sf = null;
+ this.ft = null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void accumulate(NamedList stv) {
+ if (computeCount) {
+ count += (Long) stv.get("count");
+ }
+ if (computeMissing) {
+ missing += (Long) stv.get("missing");
+ }
+ if (computeCalcDistinct) {
+ distinctValues.addAll((Collection) stv.get("distinctValues"));
+ countDistinct = distinctValues.size();
+ }
+
+ if (computeMinOrMax) {
+ updateMinMax((T) stv.get("min"), (T) stv.get("max"));
+ }
+
+ if (computeCardinality) {
+ byte[] data = (byte[]) stv.get("cardinality");
+ HLL other = HLL.fromBytes(data);
+ if (hll.getType().equals(HLLType.EMPTY)) {
+ // The HLL.union method goes out of it's way not to modify the "other" HLL.
+ // Which means in the case of merging into an "EMPTY" HLL (garunteed to happen at
+ // least once in every coordination of shard requests) it always clones all
+ // of the internal storage -- but since we're going to throw "other" away after
+ // the merge, this just means a short term doubling of RAM that we can skip.
+ hll = other;
+ } else {
+ hll.union(other);
+ }
+ }
+
+ updateTypeSpecificStats(stv);
+
+ NamedList f = (NamedList) stv.get(FACETS);
+ if (f == null) {
+ return;
+ }
+
+ for (int i = 0; i < f.size(); i++) {
+ String field = f.getName(i);
+ NamedList vals = (NamedList) f.getVal(i);
+ Map addTo = facets.get(field);
+ if (addTo == null) {
+ addTo = new HashMap<>();
+ facets.put(field, addTo);
+ }
+ for (int j = 0; j < vals.size(); j++) {
+ String val = vals.getName(j);
+ StatsValues vvals = addTo.get(val);
+ if (vvals == null) {
+ vvals = StatsValuesFactory.createStatsValues(statsField);
+ addTo.put(val, vvals);
+ }
+ vvals.accumulate((NamedList) vals.getVal(j));
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void accumulate(BytesRef value, int count) {
+ if (null == ft) {
+ throw new IllegalStateException(
+ "Can't collect & convert BytesRefs on stats that do't use a a FieldType: "
+ + statsField);
+ }
+ T typedValue = (T) ft.toObject(sf, value);
+ accumulate(typedValue, count);
+ }
+
+ public void accumulate(T value, int count) {
+ assert null != value : "Can't accumulate null";
+
+ if (computeCount) {
+ this.count += count;
+ }
+ if (computeCalcDistinct) {
+ distinctValues.add(value);
+ countDistinct = distinctValues.size();
+ }
+ if (computeMinOrMax) {
+ updateMinMax(value, value);
+ }
+ if (computeCardinality) {
+ if (null == hasher) {
+ assert value instanceof Number : "pre-hashed value support only works with numeric longs";
+ hll.addRaw(((Number)value).longValue());
+ } else {
+ hll.addRaw(hash(value));
+ }
+ }
+ updateTypeSpecificStats(value, count);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void missing() {
+ if (computeMissing) {
+ missing++;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addMissing(int count) {
+ missing += count;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addFacet(String facetName, Map facetValues) {
+ facets.put(facetName, facetValues);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public NamedList> getStatsValues() {
+ NamedList