Merge pull request #8613 from atmire/issue-1712_w2p-97080_facet-search-all-words-main

Facet search should search all words, not just the first one
This commit is contained in:
Tim Donohue
2023-01-20 09:37:06 -06:00
committed by GitHub
7 changed files with 525 additions and 90 deletions

View File

@@ -7,6 +7,8 @@
*/
package org.dspace.discovery;
import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -109,6 +111,9 @@ public class DiscoverResult {
if (facetValues.size() == 0 && field.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) {
facetValues = getFacetResult(field.getIndexFieldName() + ".year");
}
if (facetValues.isEmpty()) {
facetValues = getFacetResult(field.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES);
}
return ListUtils.emptyIfNull(facetValues);
}

View File

@@ -105,6 +105,10 @@ public class SolrServiceImpl implements SearchService, IndexingService {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(SolrServiceImpl.class);
// Suffix of the solr field used to index the facet/filter so that the facet search can search all word in a
// facet by indexing "each word to end of value' partial value
public static final String SOLR_FIELD_SUFFIX_FACET_PREFIXES = "_prefix";
@Autowired
protected ContentServiceFactory contentServiceFactory;
@Autowired
@@ -905,6 +909,9 @@ public class SolrServiceImpl implements SearchService, IndexingService {
//Only add facet information if there are any facets
for (DiscoverFacetField facetFieldConfig : facetFields) {
String field = transformFacetField(facetFieldConfig, facetFieldConfig.getField(), false);
if (facetFieldConfig.getPrefix() != null) {
field = transformPrefixFacetField(facetFieldConfig, facetFieldConfig.getField(), false);
}
solrQuery.addFacetField(field);
// Setting the facet limit in this fashion ensures that each facet can have its own max
@@ -1344,7 +1351,31 @@ public class SolrServiceImpl implements SearchService, IndexingService {
}
}
/**
* Gets the solr field that contains the facet value split on each word break to the end, so can be searched
* on each word in the value, see {@link org.dspace.discovery.indexobject.ItemIndexFactoryImpl
* #saveFacetPrefixParts(SolrInputDocument, DiscoverySearchFilter, String, String)}
* Ony applicable to facets of type {@link DiscoveryConfigurationParameters.TYPE_TEXT}, otherwise uses the regular
* facet filter field
*/
protected String transformPrefixFacetField(DiscoverFacetField facetFieldConfig, String field,
boolean removePostfix) {
if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT) ||
facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) {
if (removePostfix) {
return field.substring(0, field.lastIndexOf(SOLR_FIELD_SUFFIX_FACET_PREFIXES));
} else {
return field + SOLR_FIELD_SUFFIX_FACET_PREFIXES;
}
} else {
return this.transformFacetField(facetFieldConfig, field, removePostfix);
}
}
protected String transformFacetField(DiscoverFacetField facetFieldConfig, String field, boolean removePostfix) {
if (field.contains(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) {
return this.transformPrefixFacetField(facetFieldConfig, field, removePostfix);
}
if (facetFieldConfig.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) {
if (removePostfix) {
return field.substring(0, field.lastIndexOf("_filter"));
@@ -1390,7 +1421,7 @@ public class SolrServiceImpl implements SearchService, IndexingService {
if (field.equals("location.comm") || field.equals("location.coll")) {
value = locationToName(context, field, value);
} else if (field.endsWith("_filter") || field.endsWith("_ac")
|| field.endsWith("_acid")) {
|| field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) {
//We have a filter make sure we split !
String separator = DSpaceServicesFactory.getInstance().getConfigurationService()
.getProperty("discovery.solr.facets.split.char");
@@ -1422,7 +1453,7 @@ public class SolrServiceImpl implements SearchService, IndexingService {
return value;
}
if (field.endsWith("_filter") || field.endsWith("_ac")
|| field.endsWith("_acid")) {
|| field.endsWith("_acid") || field.endsWith(SOLR_FIELD_SUFFIX_FACET_PREFIXES)) {
//We have a filter make sure we split !
String separator = DSpaceServicesFactory.getInstance().getConfigurationService()
.getProperty("discovery.solr.facets.split.char");

View File

@@ -7,6 +7,8 @@
*/
package org.dspace.discovery;
import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -261,9 +263,9 @@ public class SolrServiceMetadataBrowseIndexingPlugin implements SolrServiceIndex
}
}
}
for (String facet : distFValues) {
document.addField(bi.getDistinctTableName() + "_filter", facet);
document.addField(bi.getDistinctTableName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES, facet);
}
for (String facet : distFAuths) {
document.addField(bi.getDistinctTableName()

View File

@@ -7,6 +7,8 @@
*/
package org.dspace.discovery.indexobject;
import static org.dspace.discovery.SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -20,6 +22,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@@ -519,88 +523,10 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
+ var);
}
}
// if searchFilter is of type "facet", delegate to indexIfFilterTypeFacet method
if (searchFilter.getFilterType().equals(DiscoverySearchFilterFacet.FILTER_TYPE_FACET)) {
if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) {
//Add a special filter
//We use a separator to split up the lowercase and regular case, this is needed to
// get our filters in regular case
//Solr has issues with facet prefix and cases
if (authority != null) {
String facetValue = preferedLabel != null ? preferedLabel : value;
doc.addField(searchFilter.getIndexFieldName() + "_filter", facetValue
.toLowerCase() + separator + facetValue + SearchUtils.AUTHORITY_SEPARATOR
+ authority);
} else {
doc.addField(searchFilter.getIndexFieldName() + "_filter",
value.toLowerCase() + separator + value);
}
} else if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) {
if (date != null) {
String indexField = searchFilter.getIndexFieldName() + ".year";
String yearUTC = DateFormatUtils.formatUTC(date, "yyyy");
doc.addField(searchFilter.getIndexFieldName() + "_keyword", yearUTC);
// add the year to the autocomplete index
doc.addField(searchFilter.getIndexFieldName() + "_ac", yearUTC);
doc.addField(indexField, yearUTC);
if (yearUTC.startsWith("0")) {
doc.addField(
searchFilter.getIndexFieldName()
+ "_keyword",
yearUTC.replaceFirst("0*", ""));
// add date without starting zeros for autocomplete e filtering
doc.addField(
searchFilter.getIndexFieldName()
+ "_ac",
yearUTC.replaceFirst("0*", ""));
doc.addField(
searchFilter.getIndexFieldName()
+ "_ac",
value.replaceFirst("0*", ""));
doc.addField(
searchFilter.getIndexFieldName()
+ "_keyword",
value.replaceFirst("0*", ""));
}
//Also save a sort value of this year, this is required for determining the upper
// & lower bound year of our facet
if (doc.getField(indexField + "_sort") == null) {
//We can only add one year so take the first one
doc.addField(indexField + "_sort", yearUTC);
}
}
} else if (searchFilter.getType()
.equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) {
HierarchicalSidebarFacetConfiguration hierarchicalSidebarFacetConfiguration =
(HierarchicalSidebarFacetConfiguration) searchFilter;
String[] subValues = value.split(hierarchicalSidebarFacetConfiguration.getSplitter());
if (hierarchicalSidebarFacetConfiguration
.isSkipFirstNodeLevel() && 1 < subValues.length) {
//Remove the first element of our array
subValues = (String[]) ArrayUtils.subarray(subValues, 1, subValues.length);
}
for (int i = 0; i < subValues.length; i++) {
StringBuilder valueBuilder = new StringBuilder();
for (int j = 0; j <= i; j++) {
valueBuilder.append(subValues[j]);
if (j < i) {
valueBuilder.append(hierarchicalSidebarFacetConfiguration.getSplitter());
}
}
String indexValue = valueBuilder.toString().trim();
doc.addField(searchFilter.getIndexFieldName() + "_tax_" + i + "_filter",
indexValue.toLowerCase() + separator + indexValue);
//We add the field x times that it has occurred
for (int j = i; j < subValues.length; j++) {
doc.addField(searchFilter.getIndexFieldName() + "_filter",
indexValue.toLowerCase() + separator + indexValue);
doc.addField(searchFilter.getIndexFieldName() + "_keyword", indexValue);
}
}
}
indexIfFilterTypeFacet(doc, searchFilter, value, date,
authority, preferedLabel, separator);
}
}
}
@@ -796,4 +722,140 @@ public class ItemIndexFactoryImpl extends DSpaceObjectIndexFactoryImpl<Indexable
final Item item = itemService.find(context, UUID.fromString(id));
return item == null ? Optional.empty() : Optional.of(new IndexableItem(item));
}
/**
* Handles indexing when discoverySearchFilter is of type facet.
*
* @param doc the solr document
* @param searchFilter the discoverySearchFilter
* @param value the metadata value
* @param date Date object
* @param authority the authority key
* @param preferedLabel the preferred label for metadata field
* @param separator the separator being used to separate lowercase and regular case
*/
private void indexIfFilterTypeFacet(SolrInputDocument doc, DiscoverySearchFilter searchFilter, String value,
Date date, String authority, String preferedLabel, String separator) {
if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_TEXT)) {
//Add a special filter
//We use a separator to split up the lowercase and regular case, this is needed to
// get our filters in regular case
//Solr has issues with facet prefix and cases
if (authority != null) {
String facetValue = preferedLabel != null ? preferedLabel : value;
doc.addField(searchFilter.getIndexFieldName() + "_filter", facetValue
.toLowerCase() + separator + facetValue + SearchUtils.AUTHORITY_SEPARATOR
+ authority);
} else {
doc.addField(searchFilter.getIndexFieldName() + "_filter",
value.toLowerCase() + separator + value);
}
//Also add prefix field with all parts of value
saveFacetPrefixParts(doc, searchFilter, value, separator, authority, preferedLabel);
} else if (searchFilter.getType().equals(DiscoveryConfigurationParameters.TYPE_DATE)) {
if (date != null) {
String indexField = searchFilter.getIndexFieldName() + ".year";
String yearUTC = DateFormatUtils.formatUTC(date, "yyyy");
doc.addField(searchFilter.getIndexFieldName() + "_keyword", yearUTC);
// add the year to the autocomplete index
doc.addField(searchFilter.getIndexFieldName() + "_ac", yearUTC);
doc.addField(indexField, yearUTC);
if (yearUTC.startsWith("0")) {
doc.addField(
searchFilter.getIndexFieldName()
+ "_keyword",
yearUTC.replaceFirst("0*", ""));
// add date without starting zeros for autocomplete e filtering
doc.addField(
searchFilter.getIndexFieldName()
+ "_ac",
yearUTC.replaceFirst("0*", ""));
doc.addField(
searchFilter.getIndexFieldName()
+ "_ac",
value.replaceFirst("0*", ""));
doc.addField(
searchFilter.getIndexFieldName()
+ "_keyword",
value.replaceFirst("0*", ""));
}
//Also save a sort value of this year, this is required for determining the upper
// & lower bound year of our facet
if (doc.getField(indexField + "_sort") == null) {
//We can only add one year so take the first one
doc.addField(indexField + "_sort", yearUTC);
}
}
} else if (searchFilter.getType()
.equals(DiscoveryConfigurationParameters.TYPE_HIERARCHICAL)) {
HierarchicalSidebarFacetConfiguration hierarchicalSidebarFacetConfiguration =
(HierarchicalSidebarFacetConfiguration) searchFilter;
String[] subValues = value.split(hierarchicalSidebarFacetConfiguration.getSplitter());
if (hierarchicalSidebarFacetConfiguration
.isSkipFirstNodeLevel() && 1 < subValues.length) {
//Remove the first element of our array
subValues = (String[]) ArrayUtils.subarray(subValues, 1, subValues.length);
}
for (int i = 0; i < subValues.length; i++) {
StringBuilder valueBuilder = new StringBuilder();
for (int j = 0; j <= i; j++) {
valueBuilder.append(subValues[j]);
if (j < i) {
valueBuilder.append(hierarchicalSidebarFacetConfiguration.getSplitter());
}
}
String indexValue = valueBuilder.toString().trim();
doc.addField(searchFilter.getIndexFieldName() + "_tax_" + i + "_filter",
indexValue.toLowerCase() + separator + indexValue);
//We add the field x times that it has occurred
for (int j = i; j < subValues.length; j++) {
doc.addField(searchFilter.getIndexFieldName() + "_filter",
indexValue.toLowerCase() + separator + indexValue);
doc.addField(searchFilter.getIndexFieldName() + "_keyword", indexValue);
}
}
//Also add prefix field with all parts of value
saveFacetPrefixParts(doc, searchFilter, value, separator, authority, preferedLabel);
}
}
/**
* Stores every "value part" in lowercase, together with the original value in regular case,
* separated by the separator, in the {fieldName}{@link SolrServiceImpl.SOLR_FIELD_SUFFIX_FACET_PREFIXES} field.
* <br>
* E.g. Author "With Multiple Words" gets stored as:
* <br>
* <code>
* with multiple words ||| With Multiple Words, <br>
* multiple words ||| With Multiple Words, <br>
* words ||| With Multiple Words, <br>
* </code>
* in the author_prefix field.
* @param doc the solr document
* @param searchFilter the current discoverySearchFilter
* @param value the metadata value
* @param separator the separator being used to separate value part and original value
*/
private void saveFacetPrefixParts(SolrInputDocument doc, DiscoverySearchFilter searchFilter, String value,
String separator, String authority, String preferedLabel) {
value = StringUtils.normalizeSpace(value);
Pattern pattern = Pattern.compile("\\b\\w+\\b", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(value);
while (matcher.find()) {
int index = matcher.start();
String currentPart = StringUtils.substring(value, index);
if (authority != null) {
String facetValue = preferedLabel != null ? preferedLabel : currentPart;
doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES,
facetValue.toLowerCase() + separator + value
+ SearchUtils.AUTHORITY_SEPARATOR + authority);
} else {
doc.addField(searchFilter.getIndexFieldName() + SOLR_FIELD_SUFFIX_FACET_PREFIXES,
currentPart.toLowerCase() + separator + value);
}
}
}
}

View File

@@ -428,8 +428,9 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
getClient().perform(get("/api/discover/facets/author")
.param("prefix", "Smith"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values",
containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("Smith, John"))));
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entryFacetWithoutSelfLink("Smith, John"),
FacetValueMatcher.entryFacetWithoutSelfLink("stIjn, SmITH"))));
getClient().perform(get("/api/discover/facets/author")
.param("prefix", "S"))
@@ -438,6 +439,9 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
.entryFacetWithoutSelfLink("Smith, John"),
FacetValueMatcher
.entryFacetWithoutSelfLink("SIdan, Mo"),
// gets returned once for smith, once for stijn
FacetValueMatcher
.entryFacetWithoutSelfLink("stIjn, SmITH"),
FacetValueMatcher
.entryFacetWithoutSelfLink("stIjn, SmITH"))));
@@ -494,12 +498,99 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("I.T."))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "?U"))
.param("prefix", "U"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values",
containsInAnyOrder(FacetValueMatcher.entryFacetWithoutSelfLink("?Unknown"))));
}
@Test
public void discoverFacetsAuthorTestWithPrefixFirstName() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community").build();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Parent Collection").build();
Item item1 = ItemBuilder.createItem(context, collection)
.withTitle("Item 1")
.withAuthor("Smith, John")
.build();
Item item2 = ItemBuilder.createItem(context, collection)
.withTitle("Item 2")
.withAuthor("Smith, Jane")
.build();
context.restoreAuthSystemState();
getClient().perform(get("/api/discover/facets/author")
.param("prefix", "john"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values",
containsInAnyOrder(
FacetValueMatcher.entryAuthor("Smith, John"))));
getClient().perform(get("/api/discover/facets/author")
.param("prefix", "jane"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values",
containsInAnyOrder(
FacetValueMatcher.entryAuthor("Smith, Jane"))));
getClient().perform(get("/api/discover/facets/author")
.param("prefix", "j"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values",
containsInAnyOrder(
FacetValueMatcher.entryAuthor("Smith, John"),
FacetValueMatcher.entryAuthor("Smith, Jane"))));
}
@Test
public void discoverFacetsAuthorWithAuthorityTestWithPrefixFirstName() throws Exception {
configurationService.setProperty("choices.plugin.dc.contributor.author", "SolrAuthorAuthority");
configurationService.setProperty("authority.controlled.dc.contributor.author", "true");
metadataAuthorityService.clearCache();
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community").build();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Parent Collection").build();
Item item1 = ItemBuilder.createItem(context, collection)
.withTitle("Item 1")
.withAuthor("Smith, John", "test_authority_1", Choices.CF_ACCEPTED)
.build();
Item item2 = ItemBuilder.createItem(context, collection)
.withTitle("Item 2")
.withAuthor("Smith, Jane", "test_authority_2", Choices.CF_ACCEPTED)
.build();
context.restoreAuthSystemState();
getClient().perform(get("/api/discover/facets/author")
.param("prefix", "j"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values",
containsInAnyOrder(
FacetValueMatcher.entryAuthorWithAuthority(
"Smith, John", "test_authority_1", 1),
FacetValueMatcher.entryAuthorWithAuthority(
"Smith, Jane", "test_authority_2", 1))));
DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig();
metadataAuthorityService.clearCache();
}
@Test
public void discoverFacetsAuthorTestForHasMoreFalse() throws Exception {
//Turn of the authorization system so that we can create the structure specified below
@@ -5987,4 +6078,240 @@ public class DiscoveryRestControllerIT extends AbstractControllerIntegrationTest
.andExpect(jsonPath("$._embedded.values").value(Matchers.hasSize(1)));
}
@Test
public void discoverFacetsSubjectTestWithCapitalAndSpecialChars() throws Exception {
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community").build();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Parent Collection").build();
Item item1 = ItemBuilder.createItem(context, collection)
.withTitle("Item 1")
.withSubject("Value with: Multiple Words ")
.build();
Item item2 = ItemBuilder.createItem(context, collection)
.withTitle("Item 2")
.withSubject("Multiple worded subject ")
.build();
Item item3 = ItemBuilder.createItem(context, collection)
.withTitle("Item 3")
.withSubject("Subject with a lot of Word values")
.build();
Item item4 = ItemBuilder.createItem(context, collection)
.withTitle("Item 4")
.withSubject("With, Values")
.build();
Item item5 = ItemBuilder.createItem(context, collection)
.withTitle("Item 5")
.withSubject("Test:of:the:colon")
.build();
Item item6 = ItemBuilder.createItem(context, collection)
.withTitle("Item 6")
.withSubject("Test,of,comma")
.build();
Item item7 = ItemBuilder.createItem(context, collection)
.withTitle("Item 7")
.withSubject("Nguyen")
.build();
Item item8 = ItemBuilder.createItem(context, collection)
.withTitle("Item 8")
.withSubject("test;Semicolon")
.build();
Item item9 = ItemBuilder.createItem(context, collection)
.withTitle("Item 9")
.withSubject("test||of|Pipe")
.build();
Item item10 = ItemBuilder.createItem(context, collection)
.withTitle("Item 10")
.withSubject("Test-Subject")
.build();
context.restoreAuthSystemState();
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "with a lot of word"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "multiple words"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Value with: Multiple Words", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "mUltiPle wor"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Multiple worded subject", 1),
FacetValueMatcher.entrySubject("Value with: Multiple Words", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "with"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("With, Values", 1),
FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1),
FacetValueMatcher.entrySubject("Value with: Multiple Words", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "of"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1),
FacetValueMatcher.entrySubject("Test,of,comma", 1),
FacetValueMatcher.entrySubject("Test:of:the:colon", 1),
FacetValueMatcher.entrySubject("test||of|Pipe", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "tEsT"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Test,of,comma", 1),
FacetValueMatcher.entrySubject("Test-Subject", 1),
FacetValueMatcher.entrySubject("Test:of:the:colon", 1),
FacetValueMatcher.entrySubject("test;Semicolon", 1),
FacetValueMatcher.entrySubject("test||of|Pipe", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "colon"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Test:of:the:colon", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "coMma"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Test,of,comma", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "guyen"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Nguyen", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "semiColon"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("test;Semicolon", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "pipe"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("test||of|Pipe", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "Subject"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubject("Multiple worded subject", 1),
FacetValueMatcher.entrySubject("Test-Subject", 1),
FacetValueMatcher.entrySubject("Subject with a lot of Word values", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "Subject of word"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values").isEmpty())
.andExpect(jsonPath("$.page.number", is(0)));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "Value with words"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values").isEmpty())
.andExpect(jsonPath("$.page.number", is(0)));
}
@Test
public void discoverFacetsSubjectWithAuthorityTest() throws Exception {
configurationService.setProperty("choices.plugin.dc.subject", "SolrSubjectAuthority");
configurationService.setProperty("authority.controlled.dc.subject", "true");
metadataAuthorityService.clearCache();
context.turnOffAuthorisationSystem();
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community").build();
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Parent Collection").build();
Item item1 = ItemBuilder.createItem(context, collection)
.withTitle("Item 1")
.withSubject("Value with: Multiple Words",
"test_authority_1", Choices.CF_ACCEPTED)
.build();
Item item2 = ItemBuilder.createItem(context, collection)
.withTitle("Item 2")
.withSubject("Multiple worded subject ",
"test_authority_2", Choices.CF_ACCEPTED)
.build();
Item item3 = ItemBuilder.createItem(context, collection)
.withTitle("Item 3")
.withSubject("Subject with a lot of Word values",
"test_authority_3", Choices.CF_ACCEPTED)
.build();
context.restoreAuthSystemState();
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "with a lot of word"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubjectWithAuthority("Subject with a lot of Word values",
"test_authority_3", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "mUltiPle wor"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubjectWithAuthority("Multiple worded subject",
"test_authority_2", 1),
FacetValueMatcher.entrySubjectWithAuthority("Value with: Multiple Words",
"test_authority_1", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "Subject"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values", containsInAnyOrder(
FacetValueMatcher.entrySubjectWithAuthority("Multiple worded subject",
"test_authority_2", 1),
FacetValueMatcher.entrySubjectWithAuthority("Subject with a lot of Word values",
"test_authority_3", 1))));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "Subject of word"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values").isEmpty())
.andExpect(jsonPath("$.page.number", is(0)));
getClient().perform(get("/api/discover/facets/subject")
.param("prefix", "Value with words"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._embedded.values").isEmpty())
.andExpect(jsonPath("$.page.number", is(0)));
DSpaceServicesFactory.getInstance().getConfigurationService().reloadConfig();
metadataAuthorityService.clearCache();
}
}

View File

@@ -55,15 +55,20 @@ public class FacetValueMatcher {
hasJsonPath("$.type", is("discover")),
hasJsonPath("$.count", is(count)),
hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")),
hasJsonPath("$._links.search.href", containsString("f.subject=" + label + ",equals"))
hasJsonPath("$._links.search.href", containsString(
"f.subject=" + urlPathSegmentEscaper().escape(label) + ",equals"))
);
}
public static Matcher<? super Object> entrySubject(String label, String authority, int count) {
public static Matcher<? super Object> entrySubjectWithAuthority(String label, String authority, int count) {
return allOf(
hasJsonPath("$.authorityKey", is(authority)),
entrySubject(label, count)
hasJsonPath("$.count", is(count)),
hasJsonPath("$.label", is(label)),
hasJsonPath("$.type", is("discover")),
hasJsonPath("$._links.search.href", containsString("api/discover/search/objects")),
hasJsonPath("$._links.search.href", containsString("f.subject=" + authority + ",authority"))
);
}

View File

@@ -299,6 +299,9 @@
<!--Dynamic field used for sidebar filters & SOLR browse by value -->
<dynamicField name="*_filter" type="keywordFilter" indexed="true" stored="true" multiValued="true" omitNorms="true" />
<!--Dynamic field used to index the facet/filter value, split on each word to end,
so that the facet search can search all words in a facet-->
<dynamicField name="*_prefix" type="keywordFilter" indexed="true" stored="true" multiValued="true" omitNorms="true" />
<dynamicField name="*_authority" type="keywordFilter" indexed="true" stored="true" multiValued="true" omitNorms="true" />
<dynamicField name="*_keyword" type="keywordFilter" indexed="true" stored="true" multiValued="true" omitNorms="true" />