diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualListController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualListController.java index 012bb6c767..3b74a2208b 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualListController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualListController.java @@ -6,9 +6,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; 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.logging.Log; import org.apache.commons.logging.LogFactory; @@ -16,6 +18,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.VClass; 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.freemarker.responsevalues.ExceptionResponseValues; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; @@ -27,8 +30,7 @@ import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; import edu.cornell.mannlib.vitro.webapp.utils.searchengine.SearchQueryUtils; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividual; - -import javax.servlet.annotation.WebServlet; +import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividualBuilder; /** * 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 String TEMPLATE_DEFAULT = "individualList.ftl"; + private static final String LANGUAGE_FILTER_PROPERTY = "RDFService.languageFilter"; @Override protected ResponseValues processRequest(VitroRequest vreq) { @@ -152,12 +155,12 @@ public static int getPageParameter(VitroRequest 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{ try{ - List classUris = Collections.singletonList(vclassURI); - IndividualListQueryResults results = buildAndExecuteVClassQuery(classUris, alpha, page, INDIVIDUALS_PER_PAGE, vreq.getWebappDaoFactory().getIndividualDao()); - return getResultsForVClassQuery(results, page, INDIVIDUALS_PER_PAGE, alpha, vreq); + List classUris = Collections.singletonList(vclassURI); + return buildAndExecuteVClassQuery(classUris, page, INDIVIDUALS_PER_PAGE, alpha, vreq); } catch (SearchEngineException e) { String msg = "An error occurred retrieving results for vclass query"; log.error(msg, e); @@ -169,16 +172,27 @@ public static IndividualListResults getResultsForVClass(String vclassURI, int pa } } - public static IndividualListResults getResultsForVClassIntersections(List vclassURIs, int page, int pageSize, String alpha, VitroRequest vreq) { + public static IndividualListResults getResultsForVClassIntersections( + List classUris, int page, int pageSize, String alpha, VitroRequest vreq) { try{ - IndividualListQueryResults results = buildAndExecuteVClassQuery(vclassURIs, alpha, page, pageSize, vreq.getWebappDaoFactory().getIndividualDao()); - return getResultsForVClassQuery(results, page, pageSize, alpha, vreq); + return buildAndExecuteVClassQuery(classUris, page, pageSize, alpha, vreq); } 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; } } + private static IndividualListResults buildAndExecuteVClassQuery(List 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) { try{ List classUris = Collections.singletonList(vclassURI); @@ -201,9 +215,10 @@ private static IndividualListResults getResultsForVClassQuery(IndividualListQuer private static IndividualListQueryResults buildAndExecuteVClassQuery( - List vclassURIs, String alpha, int page, int pageSize, IndividualDao indDao) + List vclassURIs, String alpha, Locale locale, int page, + int pageSize, IndividualDao indDao) 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); log.debug("Executed search query for " + vclassURIs); if (results.getIndividuals().isEmpty()) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/VitroSearchTermNames.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/VitroSearchTermNames.java index 1469e23cfe..2822f90544 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/VitroSearchTermNames.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/VitroSearchTermNames.java @@ -70,5 +70,11 @@ public class VitroSearchTermNames { /** Source institution name */ 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"; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/AutocompleteController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/AutocompleteController.java index ab16e25180..db79eb5f09 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/AutocompleteController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/AutocompleteController.java @@ -36,6 +36,7 @@ import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames; +import edu.cornell.mannlib.vitro.webapp.utils.searchengine.SearchQueryUtils; /** * AutocompleteController generates autocomplete content @@ -127,7 +128,10 @@ else if ( parts.length == 1 ) { for (SearchResultDocument doc : docs) { try { 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 String mst = doc.getStringValue(VitroSearchTermNames.MOST_SPECIFIC_TYPE_URIS); //Assuming these will get me string values @@ -184,7 +188,9 @@ private SearchQuery getQuery(String queryStr, VitroRequest vreq) { 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. // query.addSortField(VitroSearchTermNames.NAME_LOWERCASE, Order.ASC); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/base/BaseSearchQuery.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/base/BaseSearchQuery.java index 4e32560639..83f6b5b343 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/base/BaseSearchQuery.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/base/BaseSearchQuery.java @@ -5,8 +5,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -21,7 +21,7 @@ public class BaseSearchQuery implements SearchQuery { private int rows = -1; private final Set fieldsToReturn = new HashSet<>(); - private final Map sortFields = new HashMap<>(); + private final Map sortFields = new LinkedHashMap <>(); private final Set filters = new HashSet<>(); private final Set facetFields = new HashSet<>(); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrFieldInitializer.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrFieldInitializer.java new file mode 100644 index 0000000000..ce42756427 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrFieldInitializer.java @@ -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 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 fieldSuffixes, ConcurrentUpdateSolrClient updateEngine) + throws Exception { + for (String suffix : fieldSuffixes) { + Map 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 getFieldAttributes(String suffix) { + Map fieldAttributes = new HashMap(); + fieldAttributes.put("type", "string"); + fieldAttributes.put("stored", "true"); + fieldAttributes.put("indexed", "true"); + fieldAttributes.put("name", "*" + suffix); + return fieldAttributes; + } + + private static void excludeMatchedFields(Set fieldSuffixes, SolrClient queryEngine, String fieldType) + throws Exception { + SolrQuery query = new SolrQuery(); + query.add(CommonParams.QT, "/schema/" + fieldType.toLowerCase()); + QueryResponse response = queryEngine.query(query); + ArrayList fieldList = (ArrayList) response.getResponse().get(fieldType); + if (fieldList == null) { + return; + } + Set 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); + } + } + } + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrSearchEngine.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrSearchEngine.java index fbea3bb956..e7ec7fd569 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrSearchEngine.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchengine/solr/SolrSearchEngine.java @@ -76,7 +76,9 @@ public void startup(Application application, ComponentStartupStatus css) { // no apparent 7.4.0 analogy to `setPollQueueTime(25)` updateEngine = updateBuilder.build(); - + + SolrFieldInitializer.initializeFields(queryEngine, updateEngine); + css.info("Set up the Solr search engine; URL = '" + solrServerUrlString + "'."); } catch (Exception e) { css.fatal("Could not set up the Solr search engine", e); diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java index d4ead0f4aa..ff63a1c88d 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java @@ -52,13 +52,13 @@ public class SelectQueryDocumentModifier implements DocumentModifier, private static final Log log = LogFactory .getLog(SelectQueryDocumentModifier.class); - private RDFService rdfService; + protected RDFService rdfService; /** 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. */ - private List queries = new ArrayList<>(); + protected List queries = new ArrayList<>(); /** * The names of the fields where the results of the queries will be stored. @@ -128,7 +128,7 @@ public void modifyDocument(Individual ind, SearchInputDocument doc) { } } - private boolean passesTypeRestrictions(Individual ind) { + protected boolean passesTypeRestrictions(Individual ind) { if (typeRestrictions.isEmpty()) { return true; } else { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryI18nDocumentModifier.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryI18nDocumentModifier.java new file mode 100644 index 0000000000..89d81ebcfb --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryI18nDocumentModifier.java @@ -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 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>> maps = getTextForQueries(ind); + for (Map> map : maps) { + for (String locale : map.keySet()) { + List values = map.get(locale); + String fieldName = locale + fieldSuffix; + doc.addField(fieldName, values); + } + } + } + } + + protected List>> getTextForQueries(Individual ind) { + List>> list = new ArrayList<>(); + for (String query : queries) { + list.add(getTextForQuery(query, ind)); + } + return list; + } + + protected Map> getTextForQuery(String query, Individual ind) { + try { + QueryHolder queryHolder = new QueryHolder(query).bindToUri("uri", ind.getURI()); + Map> mapLocaleToFields = new HashMap<>(); + for (String locale : locales) { + LanguageFilteringRDFService lfrs = new LanguageFilteringRDFService(rdfService, + Collections.singletonList(locale)); + List 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); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationReader.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationReader.java new file mode 100644 index 0000000000..29d01996d5 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/ConfigurationReader.java @@ -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); +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java index 5133bf2a4c..9e889426ce 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/configuration/WrappedInstance.java @@ -12,6 +12,7 @@ import javax.servlet.ServletContext; 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.utils.configuration.PropertyType.PropertyMethod; import edu.cornell.mannlib.vitro.webapp.utils.configuration.PropertyType.PropertyStatement; @@ -62,6 +63,15 @@ public void satisfyInterfaces(ServletContext ctx, HttpServletRequest 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)); + } + } } /** diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/searchengine/SearchQueryUtils.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/searchengine/SearchQueryUtils.java index a9a03e6e91..ae99b60449 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/searchengine/SearchQueryUtils.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/utils/searchengine/SearchQueryUtils.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -150,19 +151,38 @@ public static long getIndividualCount(List vclassUris) { } /** - * builds a query with a type clause for each type in vclassUris, NAME_LOWERCASE filetred by - * alpha, and just the hits for the page for pageSize. + * builds a query with a type clause for each type in vclassUris, + * 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 vclassUris, String alpha, int page, int pageSize){ + public static SearchQuery getQuery(List vclassUris, String alpha, + Locale locale, int page, int pageSize){ String queryText = ""; SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine(); try { queryText = makeMultiClassQuery(vclassUris); - + + String localeSpecificField = null; + + if (locale != null) { + localeSpecificField = getSortFieldNameForLocale(locale); + } + // Add alpha filter if applicable if ( alpha != null && !"".equals(alpha) && alpha.length() == 1) { - queryText += VitroSearchTermNames.NAME_LOWERCASE + ":" + alpha.toLowerCase() + "*"; + if (locale == null) { + 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); @@ -172,6 +192,11 @@ public static SearchQuery getQuery(List vclassUris, String alpha, int pa query.setStart( startRow ).setRows( pageSize ); // 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); log.debug("Query is " + query.toString()); @@ -182,6 +207,14 @@ public static SearchQuery getQuery(List vclassUris, String alpha, int pa return searchEngine.createQuery(); } } + + 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 vclassUris, int page, int pageSize){ String queryText = ""; diff --git a/home/src/main/resources/rdf/display/everytime/documentModifierI18nLabel.n3 b/home/src/main/resources/rdf/display/everytime/documentModifierI18nLabel.n3 new file mode 100644 index 0000000000..88e884b0ab --- /dev/null +++ b/home/src/main/resources/rdf/display/everytime/documentModifierI18nLabel.n3 @@ -0,0 +1,16 @@ +@prefix : . +@prefix rdfs: . +@prefix xsd: . + +:documentModifier_multilingual_label + a , + ; + rdfs:label "multilingual label document modifier" ; + :hasTargetSuffix "_label_display" ; + :hasSelectQuery """ + PREFIX rdfs: + SELECT (MIN(?label) AS ?singleLabel ) WHERE { + ?uri rdfs:label ?label . + BIND (LANG(?label) as ?lang ) + } GROUP BY ?lang ORDER BY ?lang + """ . diff --git a/home/src/main/resources/rdf/display/everytime/documentModifierI18nSort.n3 b/home/src/main/resources/rdf/display/everytime/documentModifierI18nSort.n3 new file mode 100644 index 0000000000..9c3002ff1f --- /dev/null +++ b/home/src/main/resources/rdf/display/everytime/documentModifierI18nSort.n3 @@ -0,0 +1,16 @@ +@prefix : . +@prefix rdfs: . +@prefix xsd: . + +:documentModifier_multilingual_sort + a , + ; + rdfs:label "multilingual sort document modifier" ; + :hasTargetSuffix "_label_sort" ; + :hasSelectQuery """ + PREFIX rdfs: + SELECT ( LCASE(MIN(?label)) AS ?singleLabel ) WHERE { + ?uri rdfs:label ?label . + BIND (LANG(?label) as ?lang ) + } GROUP BY ?lang ORDER BY ?lang + """ .