Issue/vivo 3606 : add language-specific sorting and label fields to search index (#321)
* Add select query document modifier with dynamic target field; use locale-specific sort fields when available. * Add i18nized labels to index for autocomplete * Remove lowercasing from label query * Improved document modifier for multilingual field with defined suffix name * Improved document modifier for multilingual field with defined suffix name * refact: reverted access modifier changes * Lowercase label in documentModifierI18nSort in case old solr schema is used which doesn't have lowercase filter * fix: fixed queries and locale names * fix: renamed new document modifier * fix: use linkedHashMap to retain map sort fields order * refact: extracted buildAndExecuteVClassQuery(List<String> classUris, int page, int pageSize, String alpha, VitroRequest vreq) * fix: removed unused import * fix: constant name aligned with other suffix Co-authored-by: Brian Lowe <brian@ontocale.com>
This commit is contained in:
parent
6ad364f9ee
commit
df3c4a88ae
13 changed files with 327 additions and 27 deletions
|
@ -6,9 +6,11 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividualBuilder;
|
import javax.servlet.annotation.WebServlet;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
@ -16,6 +18,7 @@ import org.apache.commons.logging.LogFactory;
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.VClass;
|
import edu.cornell.mannlib.vitro.webapp.beans.VClass;
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.VClassGroup;
|
import edu.cornell.mannlib.vitro.webapp.beans.VClassGroup;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||||
|
@ -27,8 +30,7 @@ import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngineExcepti
|
||||||
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery;
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery;
|
||||||
import edu.cornell.mannlib.vitro.webapp.utils.searchengine.SearchQueryUtils;
|
import edu.cornell.mannlib.vitro.webapp.utils.searchengine.SearchQueryUtils;
|
||||||
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividual;
|
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividual;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividualBuilder;
|
||||||
import javax.servlet.annotation.WebServlet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a list of individuals for display in a template
|
* Generates a list of individuals for display in a template
|
||||||
|
@ -43,6 +45,7 @@ public class IndividualListController extends FreemarkerHttpServlet {
|
||||||
private static final int MAX_PAGES = 40; // must be even
|
private static final int MAX_PAGES = 40; // must be even
|
||||||
|
|
||||||
private static final String TEMPLATE_DEFAULT = "individualList.ftl";
|
private static final String TEMPLATE_DEFAULT = "individualList.ftl";
|
||||||
|
private static final String LANGUAGE_FILTER_PROPERTY = "RDFService.languageFilter";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ResponseValues processRequest(VitroRequest vreq) {
|
protected ResponseValues processRequest(VitroRequest vreq) {
|
||||||
|
@ -152,12 +155,12 @@ public class IndividualListController extends FreemarkerHttpServlet {
|
||||||
return SearchQueryUtils.getPageParameter(request);
|
return SearchQueryUtils.getPageParameter(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IndividualListResults getResultsForVClass(String vclassURI, int page, String alpha, VitroRequest vreq)
|
public static IndividualListResults getResultsForVClass(String vclassURI,
|
||||||
|
int page, String alpha, VitroRequest vreq)
|
||||||
throws SearchException{
|
throws SearchException{
|
||||||
try{
|
try{
|
||||||
List<String> classUris = Collections.singletonList(vclassURI);
|
List<String> classUris = Collections.singletonList(vclassURI);
|
||||||
IndividualListQueryResults results = buildAndExecuteVClassQuery(classUris, alpha, page, INDIVIDUALS_PER_PAGE, vreq.getWebappDaoFactory().getIndividualDao());
|
return buildAndExecuteVClassQuery(classUris, page, INDIVIDUALS_PER_PAGE, alpha, vreq);
|
||||||
return getResultsForVClassQuery(results, page, INDIVIDUALS_PER_PAGE, alpha, vreq);
|
|
||||||
} catch (SearchEngineException e) {
|
} catch (SearchEngineException e) {
|
||||||
String msg = "An error occurred retrieving results for vclass query";
|
String msg = "An error occurred retrieving results for vclass query";
|
||||||
log.error(msg, e);
|
log.error(msg, e);
|
||||||
|
@ -169,16 +172,27 @@ public class IndividualListController extends FreemarkerHttpServlet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IndividualListResults getResultsForVClassIntersections(List<String> vclassURIs, int page, int pageSize, String alpha, VitroRequest vreq) {
|
public static IndividualListResults getResultsForVClassIntersections(
|
||||||
|
List<String> classUris, int page, int pageSize, String alpha, VitroRequest vreq) {
|
||||||
try{
|
try{
|
||||||
IndividualListQueryResults results = buildAndExecuteVClassQuery(vclassURIs, alpha, page, pageSize, vreq.getWebappDaoFactory().getIndividualDao());
|
return buildAndExecuteVClassQuery(classUris, page, pageSize, alpha, vreq);
|
||||||
return getResultsForVClassQuery(results, page, pageSize, alpha, vreq);
|
|
||||||
} catch(Throwable th) {
|
} catch(Throwable th) {
|
||||||
log.error("Error retrieving individuals corresponding to intersection multiple classes." + vclassURIs.toString(), th);
|
log.error("Error retrieving individuals corresponding to intersection multiple classes." + classUris.toString(), th);
|
||||||
return IndividualListResults.EMPTY;
|
return IndividualListResults.EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IndividualListResults buildAndExecuteVClassQuery(List<String> classUris, int page, int pageSize,
|
||||||
|
String alpha, VitroRequest vreq) throws SearchEngineException {
|
||||||
|
ConfigurationProperties props = ConfigurationProperties.getBean(vreq);
|
||||||
|
boolean languageFilter = Boolean.valueOf(props.getProperty(LANGUAGE_FILTER_PROPERTY, "false"));
|
||||||
|
IndividualListQueryResults results = buildAndExecuteVClassQuery(classUris, alpha,
|
||||||
|
((languageFilter) ? vreq.getLocale() : null), page, pageSize,
|
||||||
|
vreq.getWebappDaoFactory().getIndividualDao());
|
||||||
|
IndividualListResults indListResults = getResultsForVClassQuery(results, page, pageSize, alpha, vreq);
|
||||||
|
return indListResults;
|
||||||
|
}
|
||||||
|
|
||||||
public static IndividualListResults getRandomResultsForVClass(String vclassURI, int page, int pageSize, VitroRequest vreq) {
|
public static IndividualListResults getRandomResultsForVClass(String vclassURI, int page, int pageSize, VitroRequest vreq) {
|
||||||
try{
|
try{
|
||||||
List<String> classUris = Collections.singletonList(vclassURI);
|
List<String> classUris = Collections.singletonList(vclassURI);
|
||||||
|
@ -201,9 +215,10 @@ public class IndividualListController extends FreemarkerHttpServlet {
|
||||||
|
|
||||||
|
|
||||||
private static IndividualListQueryResults buildAndExecuteVClassQuery(
|
private static IndividualListQueryResults buildAndExecuteVClassQuery(
|
||||||
List<String> vclassURIs, String alpha, int page, int pageSize, IndividualDao indDao)
|
List<String> vclassURIs, String alpha, Locale locale, int page,
|
||||||
|
int pageSize, IndividualDao indDao)
|
||||||
throws SearchEngineException {
|
throws SearchEngineException {
|
||||||
SearchQuery query = SearchQueryUtils.getQuery(vclassURIs, alpha, page, pageSize);
|
SearchQuery query = SearchQueryUtils.getQuery(vclassURIs, alpha, locale, page, pageSize);
|
||||||
IndividualListQueryResults results = IndividualListQueryResults.runQuery(query, indDao);
|
IndividualListQueryResults results = IndividualListQueryResults.runQuery(query, indDao);
|
||||||
log.debug("Executed search query for " + vclassURIs);
|
log.debug("Executed search query for " + vclassURIs);
|
||||||
if (results.getIndividuals().isEmpty()) {
|
if (results.getIndividuals().isEmpty()) {
|
||||||
|
|
|
@ -71,4 +71,10 @@ public class VitroSearchTermNames {
|
||||||
/** Source institution name */
|
/** Source institution name */
|
||||||
public static final String SITE_NAME = "siteName";
|
public static final String SITE_NAME = "siteName";
|
||||||
|
|
||||||
|
/** Multilingual sort field suffix */
|
||||||
|
public static final String LABEL_SORT_SUFFIX = "_label_sort";
|
||||||
|
|
||||||
|
/** Multilingual label field suffix */
|
||||||
|
public static final String LABEL_DISPLAY_SUFFIX = "_label_display";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResultDocumen
|
||||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
|
||||||
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
|
||||||
import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames;
|
import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.searchengine.SearchQueryUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AutocompleteController generates autocomplete content
|
* AutocompleteController generates autocomplete content
|
||||||
|
@ -127,7 +128,10 @@ public class AutocompleteController extends VitroAjaxController {
|
||||||
for (SearchResultDocument doc : docs) {
|
for (SearchResultDocument doc : docs) {
|
||||||
try {
|
try {
|
||||||
String uri = doc.getStringValue(VitroSearchTermNames.URI);
|
String uri = doc.getStringValue(VitroSearchTermNames.URI);
|
||||||
String name = doc.getStringValue(VitroSearchTermNames.NAME_RAW);
|
String name = doc.getStringValue(SearchQueryUtils.getLabelFieldNameForLocale(vreq.getLocale()));
|
||||||
|
if (name == null) {
|
||||||
|
name = doc.getStringValue(VitroSearchTermNames.NAME_RAW);
|
||||||
|
}
|
||||||
//There may be multiple most specific types, sending them all back
|
//There may be multiple most specific types, sending them all back
|
||||||
String mst = doc.getStringValue(VitroSearchTermNames.MOST_SPECIFIC_TYPE_URIS);
|
String mst = doc.getStringValue(VitroSearchTermNames.MOST_SPECIFIC_TYPE_URIS);
|
||||||
//Assuming these will get me string values
|
//Assuming these will get me string values
|
||||||
|
@ -184,7 +188,9 @@ public class AutocompleteController extends VitroAjaxController {
|
||||||
addFilterQuery(query, typeParam, multipleTypesParam);
|
addFilterQuery(query, typeParam, multipleTypesParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
query.addFields(VitroSearchTermNames.NAME_RAW, VitroSearchTermNames.URI, VitroSearchTermNames.MOST_SPECIFIC_TYPE_URIS); // fields to retrieve
|
query.addFields(SearchQueryUtils.getLabelFieldNameForLocale(vreq.getLocale()),
|
||||||
|
VitroSearchTermNames.NAME_RAW, VitroSearchTermNames.URI,
|
||||||
|
VitroSearchTermNames.MOST_SPECIFIC_TYPE_URIS); // fields to retrieve
|
||||||
|
|
||||||
// Can't sort on multivalued field, so we sort the results in Java when we get them.
|
// Can't sort on multivalued field, so we sort the results in Java when we get them.
|
||||||
// query.addSortField(VitroSearchTermNames.NAME_LOWERCASE, Order.ASC);
|
// query.addSortField(VitroSearchTermNames.NAME_LOWERCASE, Order.ASC);
|
||||||
|
|
|
@ -5,8 +5,8 @@ package edu.cornell.mannlib.vitro.webapp.searchengine.base;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ public class BaseSearchQuery implements SearchQuery {
|
||||||
private int rows = -1;
|
private int rows = -1;
|
||||||
|
|
||||||
private final Set<String> fieldsToReturn = new HashSet<>();
|
private final Set<String> fieldsToReturn = new HashSet<>();
|
||||||
private final Map<String, SearchQuery.Order> sortFields = new HashMap<>();
|
private final Map<String, SearchQuery.Order> sortFields = new LinkedHashMap <>();
|
||||||
private final Set<String> filters = new HashSet<>();
|
private final Set<String> filters = new HashSet<>();
|
||||||
|
|
||||||
private final Set<String> facetFields = new HashSet<>();
|
private final Set<String> facetFields = new HashSet<>();
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.searchengine.solr;
|
||||||
|
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.LABEL_DISPLAY_SUFFIX;
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.LABEL_SORT_SUFFIX;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.solr.client.solrj.SolrClient;
|
||||||
|
import org.apache.solr.client.solrj.SolrQuery;
|
||||||
|
import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient;
|
||||||
|
import org.apache.solr.client.solrj.request.schema.SchemaRequest;
|
||||||
|
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||||
|
import org.apache.solr.client.solrj.response.schema.SchemaResponse;
|
||||||
|
import org.apache.solr.common.params.CommonParams;
|
||||||
|
import org.apache.solr.common.util.SimpleOrderedMap;
|
||||||
|
|
||||||
|
public class SolrFieldInitializer {
|
||||||
|
|
||||||
|
static void initializeFields(SolrClient queryEngine, ConcurrentUpdateSolrClient updateEngine) throws Exception {
|
||||||
|
Set<String> fieldSuffixes = new HashSet<>(Arrays.asList(LABEL_SORT_SUFFIX, LABEL_DISPLAY_SUFFIX));
|
||||||
|
excludeMatchedFields(fieldSuffixes, queryEngine, "dynamicFields");
|
||||||
|
excludeMatchedFields(fieldSuffixes, queryEngine, "fields");
|
||||||
|
createMissingFields(fieldSuffixes, updateEngine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createMissingFields(Set<String> fieldSuffixes, ConcurrentUpdateSolrClient updateEngine)
|
||||||
|
throws Exception {
|
||||||
|
for (String suffix : fieldSuffixes) {
|
||||||
|
Map<String, Object> fieldAttributes = getFieldAttributes(suffix);
|
||||||
|
SchemaRequest.AddDynamicField request = new SchemaRequest.AddDynamicField(fieldAttributes);
|
||||||
|
SchemaResponse.UpdateResponse response = request.process(updateEngine);
|
||||||
|
if (response.getStatus() != 0) {
|
||||||
|
throw new Exception("Creation of missing solr field '*" + suffix + "' failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> getFieldAttributes(String suffix) {
|
||||||
|
Map<String, Object> fieldAttributes = new HashMap<String, Object>();
|
||||||
|
fieldAttributes.put("type", "string");
|
||||||
|
fieldAttributes.put("stored", "true");
|
||||||
|
fieldAttributes.put("indexed", "true");
|
||||||
|
fieldAttributes.put("name", "*" + suffix);
|
||||||
|
return fieldAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void excludeMatchedFields(Set<String> fieldSuffixes, SolrClient queryEngine, String fieldType)
|
||||||
|
throws Exception {
|
||||||
|
SolrQuery query = new SolrQuery();
|
||||||
|
query.add(CommonParams.QT, "/schema/" + fieldType.toLowerCase());
|
||||||
|
QueryResponse response = queryEngine.query(query);
|
||||||
|
ArrayList<SimpleOrderedMap> fieldList = (ArrayList<SimpleOrderedMap>) response.getResponse().get(fieldType);
|
||||||
|
if (fieldList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<String> it = new HashSet<>(fieldSuffixes);
|
||||||
|
for (String target : it) {
|
||||||
|
for (SimpleOrderedMap field : fieldList) {
|
||||||
|
String fieldName = (String) field.get("name");
|
||||||
|
if (fieldName.endsWith(target)) {
|
||||||
|
fieldSuffixes.remove(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -77,6 +77,8 @@ public class SolrSearchEngine implements SearchEngine {
|
||||||
|
|
||||||
updateEngine = updateBuilder.build();
|
updateEngine = updateBuilder.build();
|
||||||
|
|
||||||
|
SolrFieldInitializer.initializeFields(queryEngine, updateEngine);
|
||||||
|
|
||||||
css.info("Set up the Solr search engine; URL = '" + solrServerUrlString + "'.");
|
css.info("Set up the Solr search engine; URL = '" + solrServerUrlString + "'.");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
css.fatal("Could not set up the Solr search engine", e);
|
css.fatal("Could not set up the Solr search engine", e);
|
||||||
|
|
|
@ -52,13 +52,13 @@ public class SelectQueryDocumentModifier implements DocumentModifier,
|
||||||
private static final Log log = LogFactory
|
private static final Log log = LogFactory
|
||||||
.getLog(SelectQueryDocumentModifier.class);
|
.getLog(SelectQueryDocumentModifier.class);
|
||||||
|
|
||||||
private RDFService rdfService;
|
protected RDFService rdfService;
|
||||||
|
|
||||||
/** A name to be used in logging, to identify this instance. */
|
/** A name to be used in logging, to identify this instance. */
|
||||||
private String label;
|
protected String label;
|
||||||
|
|
||||||
/** The queries to be executed. There must be at least one. */
|
/** The queries to be executed. There must be at least one. */
|
||||||
private List<String> queries = new ArrayList<>();
|
protected List<String> queries = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The names of the fields where the results of the queries will be stored.
|
* The names of the fields where the results of the queries will be stored.
|
||||||
|
@ -128,7 +128,7 @@ public class SelectQueryDocumentModifier implements DocumentModifier,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean passesTypeRestrictions(Individual ind) {
|
protected boolean passesTypeRestrictions(Individual ind) {
|
||||||
if (typeRestrictions.isEmpty()) {
|
if (typeRestrictions.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/* $This file is distributed under the terms of the license in LICENSE$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.searchindex.documentBuilding;
|
||||||
|
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.utils.sparqlrunner.SparqlQueryRunner.createSelectQueryContext;
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.i18n.selection.LocaleSelectionSetup.PROPERTY_SELECTABLE_LOCALES;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchInputDocument;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.rdfservice.filter.LanguageFilteringRDFService;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationReader;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.configuration.ContextModelsUser;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.utils.sparqlrunner.QueryHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variation on SelectQueryDocumentModifier where the suffix of target field is defined.
|
||||||
|
* Multiple queries are performed for each of locales configured in runtime.properties
|
||||||
|
*
|
||||||
|
* Target field names are composed of locale + fieldSuffix.
|
||||||
|
*
|
||||||
|
* Each query should contain a ?uri variable, which will be replaced by the URI
|
||||||
|
* of the individual.
|
||||||
|
*
|
||||||
|
* All of the other result fields in each row of each query will be converted to
|
||||||
|
* strings and added to the field.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SelectQueryI18nDocumentModifier extends SelectQueryDocumentModifier
|
||||||
|
implements DocumentModifier, ContextModelsUser, ConfigurationReader {
|
||||||
|
private static final Log log = LogFactory.getLog(SelectQueryI18nDocumentModifier.class);
|
||||||
|
|
||||||
|
private String fieldSuffix = "";
|
||||||
|
|
||||||
|
private ArrayList<String> locales = new ArrayList<>();
|
||||||
|
|
||||||
|
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTargetSuffix")
|
||||||
|
public void setTargetSuffix(String fieldSuffix) {
|
||||||
|
this.fieldSuffix = fieldSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void modifyDocument(Individual ind, SearchInputDocument doc) {
|
||||||
|
if (passesTypeRestrictions(ind) && StringUtils.isNotBlank(fieldSuffix)) {
|
||||||
|
List<Map<String, List<String>>> maps = getTextForQueries(ind);
|
||||||
|
for (Map<String, List<String>> map : maps) {
|
||||||
|
for (String locale : map.keySet()) {
|
||||||
|
List<String> values = map.get(locale);
|
||||||
|
String fieldName = locale + fieldSuffix;
|
||||||
|
doc.addField(fieldName, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Map<String, List<String>>> getTextForQueries(Individual ind) {
|
||||||
|
List<Map<String, List<String>>> list = new ArrayList<>();
|
||||||
|
for (String query : queries) {
|
||||||
|
list.add(getTextForQuery(query, ind));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<String, List<String>> getTextForQuery(String query, Individual ind) {
|
||||||
|
try {
|
||||||
|
QueryHolder queryHolder = new QueryHolder(query).bindToUri("uri", ind.getURI());
|
||||||
|
Map<String, List<String>> mapLocaleToFields = new HashMap<>();
|
||||||
|
for (String locale : locales) {
|
||||||
|
LanguageFilteringRDFService lfrs = new LanguageFilteringRDFService(rdfService,
|
||||||
|
Collections.singletonList(locale));
|
||||||
|
List<String> list = createSelectQueryContext(lfrs, queryHolder).execute().toStringFields().flatten();
|
||||||
|
mapLocaleToFields.put(locale, list);
|
||||||
|
log.debug(label + " for locale " + locale + " - query: '" + query + "' returns " + list);
|
||||||
|
}
|
||||||
|
return mapLocaleToFields;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.error("problem while running query '" + query + "'", t);
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConfigurationProperties(ConfigurationProperties config) {
|
||||||
|
String property = config.getProperty(PROPERTY_SELECTABLE_LOCALES);
|
||||||
|
if (!StringUtils.isBlank(property)) {
|
||||||
|
String[] values = property.trim().split("\\s*,\\s*");
|
||||||
|
for (String value : values) {
|
||||||
|
String locale = value.replace("_", "-");
|
||||||
|
addLocale(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLocale(String localeString) {
|
||||||
|
if (StringUtils.isBlank(localeString)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
locales.add(localeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* $This file is distributed under the terms of the license in LICENSE$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.utils.configuration;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the ConfigurationBeanLoader creates an instance of this class, it will
|
||||||
|
* call this method, supplying ConfigurationProperties.
|
||||||
|
*/
|
||||||
|
public interface ConfigurationReader {
|
||||||
|
void setConfigurationProperties(ConfigurationProperties properties);
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import java.util.Set;
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
|
||||||
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
|
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
|
||||||
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyMethod;
|
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyMethod;
|
||||||
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyStatement;
|
import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyStatement;
|
||||||
|
@ -62,6 +63,15 @@ public class WrappedInstance<T> {
|
||||||
rmu.setRequestModels(ModelAccess.on(req));
|
rmu.setRequestModels(ModelAccess.on(req));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (instance instanceof ConfigurationReader) {
|
||||||
|
if (ctx == null) {
|
||||||
|
throw new ResourceUnavailableException("Cannot satisfy "
|
||||||
|
+ "ConfigurationReader interface: context not available.");
|
||||||
|
} else {
|
||||||
|
ConfigurationReader cr = (ConfigurationReader) instance;
|
||||||
|
cr.setConfigurationProperties(ConfigurationProperties.getBean(ctx));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,6 +5,7 @@ package edu.cornell.mannlib.vitro.webapp.utils.searchengine;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -150,19 +151,38 @@ public class SearchQueryUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* builds a query with a type clause for each type in vclassUris, NAME_LOWERCASE filetred by
|
* builds a query with a type clause for each type in vclassUris,
|
||||||
* alpha, and just the hits for the page for pageSize.
|
* NAME_LOWERCASE filtered by alpha, and just the hits for the page for pageSize.
|
||||||
|
* @param locale may be null. If null, default sort field will be used.
|
||||||
|
* Otherwise, query will be sorted by locale-specific sort field.
|
||||||
*/
|
*/
|
||||||
public static SearchQuery getQuery(List<String> vclassUris, String alpha, int page, int pageSize){
|
public static SearchQuery getQuery(List<String> vclassUris, String alpha,
|
||||||
|
Locale locale, int page, int pageSize){
|
||||||
String queryText = "";
|
String queryText = "";
|
||||||
SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine();
|
SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
queryText = makeMultiClassQuery(vclassUris);
|
queryText = makeMultiClassQuery(vclassUris);
|
||||||
|
|
||||||
|
String localeSpecificField = null;
|
||||||
|
|
||||||
|
if (locale != null) {
|
||||||
|
localeSpecificField = getSortFieldNameForLocale(locale);
|
||||||
|
}
|
||||||
|
|
||||||
// Add alpha filter if applicable
|
// Add alpha filter if applicable
|
||||||
if ( alpha != null && !"".equals(alpha) && alpha.length() == 1) {
|
if ( alpha != null && !"".equals(alpha) && alpha.length() == 1) {
|
||||||
|
if (locale == null) {
|
||||||
queryText += VitroSearchTermNames.NAME_LOWERCASE + ":" + alpha.toLowerCase() + "*";
|
queryText += VitroSearchTermNames.NAME_LOWERCASE + ":" + alpha.toLowerCase() + "*";
|
||||||
|
} else {
|
||||||
|
// Retrieve items matching the appropriate alpha char
|
||||||
|
// on the i18ned field if that field exists. For records
|
||||||
|
// where the field does not exist, fall back to NAME_LOWERCASE
|
||||||
|
queryText += "(" + localeSpecificField + ":" + alpha.toLowerCase()
|
||||||
|
+ "* OR (-" + localeSpecificField + ":[* TO *] AND "
|
||||||
|
+ VitroSearchTermNames.NAME_LOWERCASE + ":" + alpha.toLowerCase() + "*))";
|
||||||
|
log.debug("Multiclass query text: " + queryText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchQuery query = searchEngine.createQuery(queryText);
|
SearchQuery query = searchEngine.createQuery(queryText);
|
||||||
|
@ -172,6 +192,11 @@ public class SearchQueryUtils {
|
||||||
query.setStart( startRow ).setRows( pageSize );
|
query.setStart( startRow ).setRows( pageSize );
|
||||||
|
|
||||||
// Need a single-valued field for sorting
|
// Need a single-valued field for sorting
|
||||||
|
// Sort first by sort field for locale; fall back to
|
||||||
|
// NAME_LOWERCASE_SINGLE_VALUED if not available.
|
||||||
|
if(locale != null) {
|
||||||
|
query.addSortField(localeSpecificField, Order.ASC);
|
||||||
|
}
|
||||||
query.addSortField(VitroSearchTermNames.NAME_LOWERCASE_SINGLE_VALUED, Order.ASC);
|
query.addSortField(VitroSearchTermNames.NAME_LOWERCASE_SINGLE_VALUED, Order.ASC);
|
||||||
|
|
||||||
log.debug("Query is " + query.toString());
|
log.debug("Query is " + query.toString());
|
||||||
|
@ -183,6 +208,14 @@ public class SearchQueryUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getSortFieldNameForLocale(Locale locale) {
|
||||||
|
return locale.toString().replace('_', '-') + VitroSearchTermNames.LABEL_SORT_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLabelFieldNameForLocale(Locale locale) {
|
||||||
|
return locale.toString().replace('_', '-') + VitroSearchTermNames.LABEL_DISPLAY_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
public static SearchQuery getRandomQuery(List<String> vclassUris, int page, int pageSize){
|
public static SearchQuery getRandomQuery(List<String> vclassUris, int page, int pageSize){
|
||||||
String queryText = "";
|
String queryText = "";
|
||||||
SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine();
|
SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine();
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
@prefix : <http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#> .
|
||||||
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||||
|
|
||||||
|
:documentModifier_multilingual_label
|
||||||
|
a <java:edu.cornell.mannlib.vitro.webapp.searchindex.documentBuilding.SelectQueryI18nDocumentModifier> ,
|
||||||
|
<java:edu.cornell.mannlib.vitro.webapp.searchindex.documentBuilding.DocumentModifier> ;
|
||||||
|
rdfs:label "multilingual label document modifier" ;
|
||||||
|
:hasTargetSuffix "_label_display" ;
|
||||||
|
:hasSelectQuery """
|
||||||
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||||
|
SELECT (MIN(?label) AS ?singleLabel ) WHERE {
|
||||||
|
?uri rdfs:label ?label .
|
||||||
|
BIND (LANG(?label) as ?lang )
|
||||||
|
} GROUP BY ?lang ORDER BY ?lang
|
||||||
|
""" .
|
|
@ -0,0 +1,16 @@
|
||||||
|
@prefix : <http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#> .
|
||||||
|
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||||
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||||
|
|
||||||
|
:documentModifier_multilingual_sort
|
||||||
|
a <java:edu.cornell.mannlib.vitro.webapp.searchindex.documentBuilding.SelectQueryI18nDocumentModifier> ,
|
||||||
|
<java:edu.cornell.mannlib.vitro.webapp.searchindex.documentBuilding.DocumentModifier> ;
|
||||||
|
rdfs:label "multilingual sort document modifier" ;
|
||||||
|
:hasTargetSuffix "_label_sort" ;
|
||||||
|
:hasSelectQuery """
|
||||||
|
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||||
|
SELECT ( LCASE(MIN(?label)) AS ?singleLabel ) WHERE {
|
||||||
|
?uri rdfs:label ?label .
|
||||||
|
BIND (LANG(?label) as ?lang )
|
||||||
|
} GROUP BY ?lang ORDER BY ?lang
|
||||||
|
""" .
|
Loading…
Add table
Reference in a new issue