From ae00b1d2866fc8cacb997f55ad8a3edf8265e119 Mon Sep 17 00:00:00 2001 From: Angel Borroy Date: Wed, 7 Apr 2021 12:29:39 +0200 Subject: [PATCH] MNT-22158: Index multivalued fields as identifiers This change requires re-indexing SOLR Core Cherry picked from 251ca00 --- .../search/FacetedSearchTest.java | 87 +++++++++++++++++++ .../alfresco/solr/SolrInformationServer.java | 19 ++-- 2 files changed, 97 insertions(+), 9 deletions(-) diff --git a/e2e-test/src/test/java/org/alfresco/test/search/functional/searchServices/search/FacetedSearchTest.java b/e2e-test/src/test/java/org/alfresco/test/search/functional/searchServices/search/FacetedSearchTest.java index 76e069d24..badda5f6e 100644 --- a/e2e-test/src/test/java/org/alfresco/test/search/functional/searchServices/search/FacetedSearchTest.java +++ b/e2e-test/src/test/java/org/alfresco/test/search/functional/searchServices/search/FacetedSearchTest.java @@ -27,7 +27,9 @@ package org.alfresco.test.search.functional.searchServices.search; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.alfresco.rest.search.FacetFieldBucket; import org.alfresco.rest.search.FacetQuery; @@ -40,8 +42,12 @@ import org.alfresco.rest.search.RestResultBucketsModel; import org.alfresco.rest.search.SearchRequest; import org.alfresco.rest.search.SearchResponse; import org.alfresco.search.TestGroup; +import org.alfresco.utility.model.FileModel; +import org.alfresco.utility.model.FileType; import org.alfresco.utility.testrail.ExecutionType; import org.alfresco.utility.testrail.annotation.TestRail; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; import org.testng.Assert; import org.testng.TestException; import org.testng.annotations.BeforeClass; @@ -339,4 +345,85 @@ public class FacetedSearchTest extends AbstractSearchServicesE2ETest bucket1.assertThat().field("filterQuery").is("modifier:\"" + testUser.getUsername() + "\""); bucket1.assertThat().field("metrics").is("[{entry=null, type=count, value={count=1}}]"); } + + /** + * Test that facet fields return results for single and multivalued fields. + * { + * "query": { + * "query": "cm:addressee:'first'" + * }, + * "facetFields": { + * "facets": [{"field": "cm:addressee"}, {"field": "cm:addressees"}] + * }, + * "facetFormat":"V2" + * } + */ + @Test + @TestRail(section = {TestGroup.REST_API, TestGroup.SEARCH }, executionType = ExecutionType.REGRESSION, + description = "Checks facet queries for the Search api, single and multi-valued properties") + public void searchWithMultiValuedFieldsFacet() throws Exception + { + + // Create properties with single (cm:addressee) and multi-valued (cm:addressees) values + FileModel emailFile = FileModel.getRandomFileModel(FileType.TEXT_PLAIN, "Email"); + + Map properties = new HashMap<>(); + properties.put(PropertyIds.OBJECT_TYPE_ID, "cmis:document"); + properties.put(PropertyIds.NAME, emailFile.getName()); + properties.put(PropertyIds.SECONDARY_OBJECT_TYPE_IDS, List.of("P:cm:emailed")); + properties.put("cm:addressee", "first"); + properties.put("cm:addressees", List.of("first", "second")); + + cmisApi.authenticateUser(testUser) + .usingSite(testSite) + .usingResource(folder) + .createFile(emailFile, properties, VersioningState.MAJOR) + .assertThat().existsInRepo(); + + String addresseeQuery = "cm:addressee:'first'"; + Assert.assertTrue(waitForIndexing(addresseeQuery, true)); + + // Search facets fields cm:addressee and cm:addressess + SearchRequest query = new SearchRequest(); + RestRequestQueryModel queryReq = new RestRequestQueryModel(); + queryReq.setQuery("cm:addressee:'first'"); + query.setQuery(queryReq); + query.setFacetFormat("V2"); + RestRequestFacetFieldsModel facetFields = new RestRequestFacetFieldsModel(); + List facets = new ArrayList<>(); + facets.add(new RestRequestFacetFieldModel("cm:addressee")); + facets.add(new RestRequestFacetFieldModel("cm:addressees")); + facetFields.setFacets(facets); + query.setFacetFields(facetFields); + + SearchResponse response = query(query); + + // Verify results + Assert.assertNull(response.getContext().getFacetsFields()); + Assert.assertNull(response.getContext().getFacetQueries()); + Assert.assertFalse(response.getContext().getFacets().isEmpty()); + + // Facets for cm:addressees (multi-valued) + RestGenericFacetResponseModel model = response.getContext().getFacets().get(0); + Assert.assertEquals(model.getLabel(), "cm:addressees"); + model.assertThat().field("label").is("cm:addressees"); + RestGenericBucketModel bucket = model.getBuckets().get(0); + bucket.assertThat().field("label").is("{en}first"); + bucket.assertThat().field("filterQuery").is("cm:addressees:\"{en}first\""); + bucket.assertThat().field("metrics").is("[{entry=null, type=count, value={count=1}}]"); + bucket = model.getBuckets().get(1); + bucket.assertThat().field("label").is("{en}second"); + bucket.assertThat().field("filterQuery").is("cm:addressees:\"{en}second\""); + bucket.assertThat().field("metrics").is("[{entry=null, type=count, value={count=1}}]"); + + // Facets for cm:addressee (singel valued) + model = response.getContext().getFacets().get(1); + Assert.assertEquals(model.getLabel(), "cm:addressee"); + model.assertThat().field("label").is("cm:addressee"); + bucket = model.getBuckets().get(0); + bucket.assertThat().field("label").is("{en}first"); + bucket.assertThat().field("filterQuery").is("cm:addressee:\"{en}first\""); + bucket.assertThat().field("metrics").is("[{entry=null, type=count, value={count=1}}]"); + + } } \ No newline at end of file diff --git a/search-services/alfresco-search/src/main/java/org/alfresco/solr/SolrInformationServer.java b/search-services/alfresco-search/src/main/java/org/alfresco/solr/SolrInformationServer.java index a2bc9cca3..c77048a63 100644 --- a/search-services/alfresco-search/src/main/java/org/alfresco/solr/SolrInformationServer.java +++ b/search-services/alfresco-search/src/main/java/org/alfresco/solr/SolrInformationServer.java @@ -428,7 +428,7 @@ public class SolrInformationServer implements InformationServer private final int contentStreamLimit; private long cleanContentLastPurged; - + // Get Paths information from Repository for a batch of nodes (true by default) // When false, Paths information is only recovered for single nodes private final boolean getPathsInNodeBatches; @@ -629,7 +629,7 @@ public class SolrInformationServer implements InformationServer dataModel = AlfrescoSolrDataModel.getInstance(); contentStreamLimit = Integer.parseInt(coreConfiguration.getProperty("alfresco.contentStreamLimit", "10000000")); - + getPathsInNodeBatches = Boolean.parseBoolean(coreConfiguration.getProperty("alfresco.metadata.getPathsInNodeBatches", "true")); props = AlfrescoSolrDataModel.getCommonConfig(); @@ -1743,7 +1743,7 @@ public class SolrInformationServer implements InformationServer .map(StringPropertyValue::getValue) .map(Boolean::parseBoolean) .orElse(true); - + addDocCmd.solrDoc = isIndexed ? populateWithMetadata( basicDocument(nodeMetaData, DOC_TYPE_NODE, PartialSolrInputDocument::new), @@ -1755,8 +1755,8 @@ public class SolrInformationServer implements InformationServer // UnindexedNodes are not indexed when solrcore property flag "recordUnindexedNodes" is set to false if (addDocCmd != null) { - processor.processAdd(addDocCmd); - } + processor.processAdd(addDocCmd); + } } } @@ -2053,7 +2053,7 @@ public class SolrInformationServer implements InformationServer // Getting Ancestor information when getting a batch of nodes from repository, // may contain large information to be stored in memory for a long time. nmdp.setIncludePaths(getPathsInNodeBatches); - + // Fetches bulk metadata nmdp.setMaxResults(Integer.MAX_VALUE); Optional> nodesMetaDataFromRepository = getNodesMetaDataFromRepository(nmdp); @@ -2256,7 +2256,7 @@ public class SolrInformationServer implements InformationServer doc.addField(FIELD_QNAME, qNameBuffer.toString()); }); } - + /** * Gets full metadata information for a given nodeId, including Paths information. * Paths information can be huge in some scenarios, so it's recommended to use @@ -2341,9 +2341,10 @@ public class SolrInformationServer implements InformationServer String storedFieldName = dataModel.getStoredTextField(propertyQName); valueHolder.accept(storedFieldName, getLocalisedValue(value, locale)); + // Add identifiers for single valued (sd) and multi-valued (md) identifier fields dataModel.getIndexedFieldNamesForProperty(propertyQName).getFields() .stream() - .filter(field -> field.getField().startsWith("text@sd___@")) + .filter(field -> field.getField().startsWith("text@sd___@") || field.getField().startsWith("text@md___@")) .forEach(field -> addStringProperty(valueHolder, field, value, locale)); } else @@ -2384,7 +2385,7 @@ public class SolrInformationServer implements InformationServer { QName propertyQName = property.getKey(); - PropertyDefinition propertyDefinition = dataModel.getPropertyDefinition(propertyQName); + PropertyDefinition propertyDefinition = dataModel.getPropertyDefinition(propertyQName); // Skip adding Alfresco Fields declared as indexed="false" to SOLR Schema if (propertyDefinition != null && propertyDefinition.isIndexed())