diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/ExtendedSearchController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/ExtendedSearchController.java index ee68733d85..41172434b3 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/ExtendedSearchController.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/ExtendedSearchController.java @@ -13,8 +13,8 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -23,11 +23,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - - import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean; import edu.cornell.mannlib.vitro.webapp.beans.Individual; @@ -57,790 +52,809 @@ import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.LinkTemplateModel; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.searchresult.IndividualSearchResult; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * Paged search controller that uses the search engine */ @WebServlet(name = "ExtendedSearchController", urlPatterns = { "/extendedsearch", "/extendedsearch.jsp", - "/extendedfedsearch", "/extendedsearchcontroller" }) + "/extendedfedsearch", "/extendedsearchcontroller" }) public class ExtendedSearchController extends FreemarkerHttpServlet { - private static final String HITS_PER_PAGE_OPTIONS = "hitsPerPageOptions"; - private static final String FACETS = "facets"; - static final Log log = LogFactory.getLog(ExtendedSearchController.class); - - protected static final int DEFAULT_HITS_PER_PAGE = 30; - private static Set hitsPerPageOptions = Stream.of(10, 30, 50).collect(Collectors.toCollection(LinkedHashSet::new)); - - protected static final int DEFAULT_MAX_HIT_COUNT = 1000; - - private static final String PARAM_XML_REQUEST = "xml"; - private static final String PARAM_CSV_REQUEST = "csv"; - private static final String PARAM_START_INDEX = "startIndex"; - private static final String PARAM_HITS_PER_PAGE = "hitsPerPage"; - private static final String PARAM_CLASSGROUP = "classgroup"; - private static final String PARAM_RDFTYPE = "type"; - public static final String PARAM_QUERY_TEXT = "querytext"; - public static final String PARAM_QUERY_SORT_BY = "sort"; - - protected static final Map> templateTable; - - protected enum Format { - HTML, XML, CSV; - } - - protected enum Result { - PAGED, ERROR, BAD_QUERY - } - - static { - templateTable = setupTemplateTable(); - } - - /** - * Overriding doGet from FreemarkerHttpController to do a page template (as - * opposed to body template) style output for XML requests. - * - * This follows the pattern in AutocompleteController.java. - */ - @Override - public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - VitroRequest vreq = new VitroRequest(request); - boolean wasXmlRequested = isRequestedFormatXml(vreq); - boolean wasCSVRequested = isRequestedFormatCSV(vreq); - if (!wasXmlRequested && !wasCSVRequested) { - super.doGet(vreq, response); - } else if (wasXmlRequested) { - try { - ResponseValues rvalues = processRequest(vreq); - - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/xml;charset=UTF-8"); - response.setHeader("Content-Disposition", "attachment; filename=search.xml"); - writeTemplate(rvalues.getTemplateName(), rvalues.getMap(), request, response); - } catch (Exception e) { - log.error(e, e); - } - } else if (wasCSVRequested) { - try { - ResponseValues rvalues = processRequest(vreq); - - response.setCharacterEncoding("UTF-8"); - response.setContentType("text/csv;charset=UTF-8"); - response.setHeader("Content-Disposition", "attachment; filename=search.csv"); - writeTemplate(rvalues.getTemplateName(), rvalues.getMap(), request, response); - } catch (Exception e) { - log.error(e, e); - } - } - } - - @Override - protected ResponseValues processRequest(VitroRequest vreq) { - - // There may be other non-html formats in the future - Format format = getFormat(vreq); - boolean wasXmlRequested = Format.XML == format; - boolean wasCSVRequested = Format.CSV == format; - log.debug("Requested format was " + (wasXmlRequested ? "xml" : "html")); - boolean wasHtmlRequested = !(wasXmlRequested || wasCSVRequested); - long startTime = System.nanoTime(); - - try { - - // make sure an IndividualDao is available - if (vreq.getWebappDaoFactory() == null || vreq.getWebappDaoFactory().getIndividualDao() == null) { - log.error("Could not get webappDaoFactory or IndividualDao"); - throw new Exception("Could not access model."); - } - IndividualDao iDao = vreq.getWebappDaoFactory().getIndividualDao(); - VClassGroupDao grpDao = vreq.getWebappDaoFactory().getVClassGroupDao(); - VClassDao vclassDao = vreq.getWebappDaoFactory().getVClassDao(); - - ApplicationBean appBean = vreq.getAppBean(); - - log.debug("IndividualDao is " + iDao.toString() + " Public classes in the classgroup are " - + grpDao.getPublicGroupsWithVClasses().toString()); - log.debug("VClassDao is " + vclassDao.toString()); - - int startIndex = getStartIndex(vreq); - int hitsPerPage = getHitsPerPage(vreq); - - String queryText = getQueryText(vreq); - log.debug("Query text is \"" + queryText + "\""); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent before read filter configurations."); - } - Map filterConfigurationsByField = SearchFiltering.readFilterConfigurations(vreq); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent before get sort configurations."); - } - Map sortConfigurations = SearchFiltering.getSortConfigurations(vreq); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent before get query configurations."); - } - SearchQuery query = getQuery(queryText, hitsPerPage, startIndex, vreq, filterConfigurationsByField, - sortConfigurations); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after get query configurations."); - } - SearchEngine search = ApplicationUtils.instance().getSearchEngine(); - SearchResponse response = null; - - try { - response = search.query(query); - } catch (Exception ex) { - String msg = makeBadSearchMessage(queryText, ex.getMessage(), vreq); - log.error("could not run search query", ex); - return doFailedSearch(msg, queryText, format, vreq); - } - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after get query execution."); - } - - if (response == null) { - log.error("Search response was null"); - return doFailedSearch(I18n.text(vreq, "error_in_search_request"), queryText, format, vreq); - } - addFacetCountersFromRequest(response, filterConfigurationsByField, vreq); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after addFacetCountersFromRequest."); - } - SearchResultDocumentList docs = response.getResults(); - if (docs == null) { - log.error("Document list for a search was null"); - return doFailedSearch(I18n.text(vreq, "error_in_search_request"), queryText, format, vreq); - } - - long hitCount = docs.getNumFound(); - log.debug("Number of hits = " + hitCount); - // if ( hitCount < 1 ) { - // return doNoHits(queryText,format, vreq); - // } - - List individuals = new ArrayList(docs.size()); - for (SearchResultDocument doc : docs) { - try { - String uri = doc.getStringValue(VitroSearchTermNames.URI); - Individual ind = iDao.getIndividualByURI(uri); - if (ind != null) { - ind.setSearchSnippet(getSnippet(doc, response)); - individuals.add(ind); - } - } catch (Exception e) { - log.error("Problem getting usable individuals from search hits. ", e); - } - } - - ParamMap pagingLinkParams = new ParamMap(); - pagingLinkParams.put(PARAM_QUERY_TEXT, queryText); - pagingLinkParams.put(PARAM_HITS_PER_PAGE, String.valueOf(hitsPerPage)); - - Enumeration paramNames = vreq.getParameterNames(); - SearchFiltering.addFiltersToPageLinks(vreq, pagingLinkParams, paramNames); - - if (wasXmlRequested) { - pagingLinkParams.put(PARAM_XML_REQUEST, "1"); - } - - /* Compile the data for the templates */ - - Map body = new HashMap(); - - String classGroupParam = vreq.getParameter(PARAM_CLASSGROUP); - log.debug("ClassGroupParam is \"" + classGroupParam + "\""); - boolean classGroupFilterRequested = false; - if (!StringUtils.isBlank(classGroupParam)) { - VClassGroup grp = grpDao.getGroupByURI(classGroupParam); - classGroupFilterRequested = true; - if (grp != null && grp.getPublicName() != null) - body.put("classGroupName", grp.getPublicName()); - } - - String typeParam = vreq.getParameter(PARAM_RDFTYPE); - boolean typeFilterRequested = false; - if (!StringUtils.isBlank(typeParam)) { - - VClass type = vclassDao.getVClassByURI(typeParam); - typeFilterRequested = true; - if (type != null && type.getName() != null) - body.put("typeName", type.getName()); - } - - /* Add ClassGroup and type refinement links to body */ - if (wasHtmlRequested) { - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent before sorting filterConfigurationsByField values."); - } - for (Entry entry : filterConfigurationsByField.entrySet()) { - entry.getValue().sortValues(); - } - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after sorting filterConfigurationsByField values."); - } - Map filtersForTemplateById = SearchFiltering.getFiltersForTemplate(filterConfigurationsByField); - body.put("filters", filtersForTemplateById); - body.put("filterGroups", SearchFiltering.readFilterGroupsConfigurations(vreq, filtersForTemplateById)); - body.put("sorting", sortConfigurations.values()); - body.put("emptySearch", isEmptySearchFilters(filterConfigurationsByField)); - if (!classGroupFilterRequested && !typeFilterRequested) { - // Search request includes no ClassGroup and no type, so add ClassGroup search - // refinement links. - body.put("classGroupLinks", getClassGroupsLinks(vreq, grpDao, docs, response, queryText)); - } else if (classGroupFilterRequested && !typeFilterRequested) { - // Search request is for a ClassGroup, so add rdf:type search refinement links - // but try to filter out classes that are subclasses - body.put("classLinks", getVClassLinks(vclassDao, docs, response, queryText)); - body.put("classGroupLinks", getClassGroupsLinks(vreq, grpDao, docs, response, queryText)); - - pagingLinkParams.put(PARAM_CLASSGROUP, classGroupParam); - - } else { - // search request is for a class so there are no more refinements - pagingLinkParams.put(PARAM_RDFTYPE, typeParam); - pagingLinkParams.put(PARAM_CLASSGROUP, classGroupParam); - body.put("classLinks", getVClassLinks(vclassDao, docs, response, queryText)); - body.put("classGroupLinks", getClassGroupsLinks(vreq, grpDao, docs, response, queryText)); - - } - } - - body.put("individuals", IndividualSearchResult.getIndividualTemplateModels(individuals, vreq)); - - body.put("querytext", queryText); - body.put("locale", vreq.getLocale().toLanguageTag()); - body.put("title", - new StringBuilder().append(appBean.getApplicationName()).append(" - ") - .append(I18n.text(vreq, "search_results_for")).append(" '").append(queryText).append("'") - .toString()); - - body.put("hitCount", hitCount); - body.put("startIndex", startIndex); - body.put(PARAM_HITS_PER_PAGE, hitsPerPage); - body.put(HITS_PER_PAGE_OPTIONS, hitsPerPageOptions); - - - body.put("pagingLinks", - getPagingLinks(startIndex, hitsPerPage, hitCount, vreq.getServletPath(), pagingLinkParams, vreq)); - - if (startIndex != 0) { - body.put("prevPage", - getPreviousPageLink(startIndex, hitsPerPage, vreq.getServletPath(), pagingLinkParams)); - } - if (startIndex < (hitCount - hitsPerPage)) { - body.put("nextPage", getNextPageLink(startIndex, hitsPerPage, vreq.getServletPath(), pagingLinkParams)); - } - - String template = templateTable.get(format).get(Result.PAGED); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent before TemplateResponseValues."); - } - TemplateResponseValues templateResponseValues = new TemplateResponseValues(template, body); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after TemplateResponseValues."); - } - return templateResponseValues; - } catch (Throwable e) { - return doSearchError(e, format); - } - } - - private long getSpentTime(long startTime) { - return (System.nanoTime() - startTime )/1000000; - } - - private Object isEmptySearchFilters(Map filterConfigurationsByField) { - for (SearchFilter filter : filterConfigurationsByField.values()) { - if (filter.isSelected()) { - return false; - } - } - return true; - } - - private void addFacetCountersFromRequest(SearchResponse response, Map filtersByField, - VitroRequest vreq) { - long startTime = System.nanoTime(); - List resultfacetFields = response.getFacetFields(); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after getFacetFields."); - } - Map> requestFiltersById = SearchFiltering.getRequestFilters(vreq); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after SearchFiltering.getRequestFilters."); - } - for (SearchFacetField resultField : resultfacetFields) { - SearchFilter searchFilter = filtersByField.get(resultField.getName()); - if (searchFilter == null) { - continue; - } - List values = resultField.getValues(); - - for (Count value : values) { - if (value.getCount() == 0) { - continue; - } - String valueName = value.getName(); - FilterValue filterValue = searchFilter.getValue(valueName); - if (filterValue == null) { - filterValue = new FilterValue(valueName); - searchFilter.addValue(filterValue); - } - if (requestFiltersById.containsKey(searchFilter.getId())) { - List requestedValues = requestFiltersById.get(searchFilter.getId()); - if (requestedValues.contains(valueName)) { - filterValue.setSelected(true); - } - if (!SearchFiltering.isEmptyValues(requestedValues)) { - searchFilter.setSelected(true); - } - } - if (searchFilter.isLocalizationRequired() && StringUtils.isBlank(filterValue.getName())) { - String label = SearchFiltering.getUriLabel(value.getName(), vreq); - if (!StringUtils.isBlank(label)) { - filterValue.setName(label); - } - } - // COUNT should be from the real results of the query - filterValue.setCount(value.getCount()); - } - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after SearchFacetField " + searchFilter.getName() + "processing."); - } - } - } - - public static String getQueryText(VitroRequest vreq) { - String query = vreq.getParameter(PARAM_QUERY_TEXT); - if (StringUtils.isBlank(query)) { - return ""; - } - return query; - } - - private int getHitsPerPage(VitroRequest vreq) { - int hitsPerPage = DEFAULT_HITS_PER_PAGE; - try { - int hits = Integer.parseInt(vreq.getParameter(PARAM_HITS_PER_PAGE)); - if (hitsPerPageOptions.contains(hits)) { - hitsPerPage = hits; - } - } catch (Throwable e) { - hitsPerPage = DEFAULT_HITS_PER_PAGE; - } - log.debug("hitsPerPage is " + hitsPerPage); - return hitsPerPage; - } - - private int getStartIndex(VitroRequest vreq) { - int startIndex = 0; - try { - startIndex = Integer.parseInt(vreq.getParameter(PARAM_START_INDEX)); - } catch (Throwable e) { - startIndex = 0; - } - log.debug("startIndex is " + startIndex); - return startIndex; - } - - /** - * Get the class groups represented for the individuals in the documents. - */ - private List getClassGroupsLinks(VitroRequest vreq, VClassGroupDao grpDao, - SearchResultDocumentList docs, SearchResponse rsp, String qtxt) { - Map cgURItoCount = new HashMap(); - - List classgroups = new ArrayList(); - List ffs = rsp.getFacetFields(); - for (SearchFacetField ff : ffs) { - if (VitroSearchTermNames.CLASSGROUP_URI.equals(ff.getName())) { - List counts = ff.getValues(); - for (Count ct : counts) { - VClassGroup vcg = grpDao.getGroupByURI(ct.getName()); - if (vcg == null) { - log.debug("could not get classgroup for URI " + ct.getName()); - } else { - classgroups.add(vcg); - cgURItoCount.put(vcg.getURI(), ct.getCount()); - } - } - } - } - - grpDao.sortGroupList(classgroups); - - VClassGroupsForRequest vcgfr = VClassGroupCache.getVClassGroups(vreq); - List classGroupLinks = new ArrayList(classgroups.size()); - for (VClassGroup vcg : classgroups) { - String groupURI = vcg.getURI(); - VClassGroup localizedVcg = vcgfr.getGroup(groupURI); - long count = cgURItoCount.get(groupURI); - if (localizedVcg.getPublicName() != null && count > 0) { - classGroupLinks.add(new VClassGroupSearchLink(qtxt, localizedVcg, count)); - } - } - return classGroupLinks; - } - - private List getVClassLinks(VClassDao vclassDao, SearchResultDocumentList docs, - SearchResponse rsp, String qtxt) { - HashSet typesInHits = getVClassUrisForHits(docs); - List classes = new ArrayList(typesInHits.size()); - Map typeURItoCount = new HashMap(); - - List ffs = rsp.getFacetFields(); - for (SearchFacetField ff : ffs) { - if (VitroSearchTermNames.RDFTYPE.equals(ff.getName())) { - List counts = ff.getValues(); - for (Count ct : counts) { - String typeUri = ct.getName(); - long count = ct.getCount(); - try { - if (VitroVocabulary.OWL_THING.equals(typeUri) || count == 0) - continue; - VClass type = vclassDao.getVClassByURI(typeUri); - if (type != null && !type.isAnonymous() && type.getName() != null && !"".equals(type.getName()) - && type.getGroupURI() != null) { // don't display classes that aren't in classgroups - typeURItoCount.put(typeUri, count); - classes.add(type); - } - } catch (Exception ex) { - if (log.isDebugEnabled()) - log.debug("could not add type " + typeUri, ex); - } - } - } - } - - classes.sort(new Comparator() { - public int compare(VClass o1, VClass o2) { - return o1.compareTo(o2); - } - }); - - List vClassLinks = new ArrayList(classes.size()); - for (VClass vc : classes) { - long count = typeURItoCount.get(vc.getURI()); - vClassLinks.add(new VClassSearchLink(qtxt, vc, count)); - } - - return vClassLinks; - } - - private HashSet getVClassUrisForHits(SearchResultDocumentList docs) { - HashSet typesInHits = new HashSet(); - for (SearchResultDocument doc : docs) { - try { - Collection types = doc.getFieldValues(VitroSearchTermNames.RDFTYPE); - if (types != null) { - for (Object o : types) { - String typeUri = o.toString(); - typesInHits.add(typeUri); - } - } - } catch (Exception e) { - log.error("problems getting rdf:type for search hits", e); - } - } - return typesInHits; - } - - private String getSnippet(SearchResultDocument doc, SearchResponse response) { - String docId = doc.getStringValue(VitroSearchTermNames.DOCID); - StringBuilder text = new StringBuilder(); - Map>> highlights = response.getHighlighting(); - if (highlights != null && highlights.get(docId) != null) { - List snippets = highlights.get(docId).get(VitroSearchTermNames.ALLTEXT); - if (snippets != null && snippets.size() > 0) { - text.append("... ").append(snippets.get(0)).append(" ..."); - } - } - return text.toString(); - } - - private SearchQuery getQuery(String queryText, int hitsPerPage, int startIndex, VitroRequest vreq, - Map filtersByField, Map sortOptions) { - // Lowercase the search term to support wildcard searches: The search engine - // applies no text - // processing to a wildcard search term. - if (StringUtils.isBlank(queryText)) { - queryText = "*:*"; - } - SearchQuery query = ApplicationUtils.instance().getSearchEngine().createQuery(queryText); - - query.setStart(startIndex).setRows(hitsPerPage); - - addSortRules(vreq, query, sortOptions); - - addDefaultVitroFacets(vreq, query); - - SearchFiltering.addFacetFieldsToQuery(filtersByField, query); - - Map filtersById = SearchFiltering.getFiltersById(filtersByField); - - SearchFiltering.addFiltersToQuery(vreq, query, filtersById); - - // ClassGroup filtering param - String classgroupParam = vreq.getParameter(PARAM_CLASSGROUP); - - // rdf:type filtering param - String typeParam = vreq.getParameter(PARAM_RDFTYPE); - - if (!StringUtils.isBlank(classgroupParam)) { - // ClassGroup filtering - log.debug("Firing classgroup query "); - log.debug("request.getParameter(classgroup) is " + classgroupParam); - query.addFilterQuery(VitroSearchTermNames.CLASSGROUP_URI + ":\"" + classgroupParam + "\""); - - // with ClassGroup filtering we want type facets - query.addFacetFields(VitroSearchTermNames.RDFTYPE).setFacetLimit(1000); - - } else if (!StringUtils.isBlank(typeParam)) { - // rdf:type filtering - log.debug("Firing type query "); - log.debug("request.getParameter(type) is " + typeParam); - query.addFilterQuery(VitroSearchTermNames.RDFTYPE + ":\"" + typeParam + "\""); - // with type filtering we don't have facets. - } else { - // When no filtering is set, we want ClassGroup facets - query.addFacetFields(VitroSearchTermNames.CLASSGROUP_URI).setFacetLimit(1000); - } - - log.debug("Query = " + query.toString()); - return query; - } - - private void addDefaultVitroFacets(VitroRequest vreq, SearchQuery query) { - String[] facets = vreq.getParameterValues(FACETS); - if (facets != null && facets.length > 0) { - query.addFacetFields(facets); - } - } - - private void addSortRules(VitroRequest vreq, SearchQuery query, Map sortOptions) { - String sortType = getSortType(vreq); - if (!StringUtils.isBlank(sortType) && sortOptions.containsKey(sortType)) { - SortConfiguration conf = sortOptions.get(sortType); - query.addSortField(conf.getField(vreq.getLocale()), conf.getSortOrder()); - conf.setSelected(true); - } - } - - private String getSortType(VitroRequest vreq) { - return vreq.getParameter(PARAM_QUERY_SORT_BY); - } - - public static class VClassGroupSearchLink extends LinkTemplateModel { - private static final String EXTENDEDSEARCH = "/extendedsearch"; - long count = 0; - - VClassGroupSearchLink(String querytext, VClassGroup classgroup, long count) { - super(classgroup.getPublicName(), EXTENDEDSEARCH, PARAM_QUERY_TEXT, querytext, PARAM_CLASSGROUP, - classgroup.getURI()); - this.count = count; - } - - public String getCount() { - return Long.toString(count); - } - } - - public static class VClassSearchLink extends LinkTemplateModel { - long count = 0; - - VClassSearchLink(String querytext, VClass type, long count) { - super(type.getName(), "/extendedsearch", PARAM_QUERY_TEXT, querytext, PARAM_RDFTYPE, type.getURI()); - this.count = count; - } - - public String getCount() { - return Long.toString(count); - } - } - - protected static List getPagingLinks(int startIndex, int hitsPerPage, long hitCount, String baseUrl, - ParamMap params, VitroRequest vreq) { - - List pagingLinks = new ArrayList(); - - // No paging links if only one page of results - if (hitCount <= hitsPerPage) { - return pagingLinks; - } - - int maxHitCount = DEFAULT_MAX_HIT_COUNT; - if (startIndex >= DEFAULT_MAX_HIT_COUNT - hitsPerPage) - maxHitCount = startIndex + DEFAULT_MAX_HIT_COUNT; - - for (int i = 0; i < hitCount; i += hitsPerPage) { - params.put(PARAM_START_INDEX, String.valueOf(i)); - if (i < maxHitCount - hitsPerPage) { - int pageNumber = i / hitsPerPage + 1; - boolean iIsCurrentPage = (i >= startIndex && i < (startIndex + hitsPerPage)); - if (iIsCurrentPage) { - pagingLinks.add(new PagingLink(pageNumber)); - } else { - pagingLinks.add(new PagingLink(pageNumber, baseUrl, params)); - } - } else { - pagingLinks.add(new PagingLink(I18n.text(vreq, "paging_link_more"), baseUrl, params)); - break; - } - } - - return pagingLinks; - } - - private String getPreviousPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) { - params.put(PARAM_START_INDEX, String.valueOf(startIndex - hitsPerPage)); - return UrlBuilder.getUrl(baseUrl, params); - } - - private String getNextPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) { - params.put(PARAM_START_INDEX, String.valueOf(startIndex + hitsPerPage)); - return UrlBuilder.getUrl(baseUrl, params); - } - - protected static class PagingLink extends LinkTemplateModel { - - PagingLink(int pageNumber, String baseUrl, ParamMap params) { - super(String.valueOf(pageNumber), baseUrl, params); - } - - // Constructor for current page item: not a link, so no url value. - PagingLink(int pageNumber) { - setText(String.valueOf(pageNumber)); - } - - // Constructor for "more..." item - PagingLink(String text, String baseUrl, ParamMap params) { - super(text, baseUrl, params); - } - } - - private ExceptionResponseValues doSearchError(Throwable e, Format f) { - Map body = new HashMap(); - body.put("message", "Search failed: " + e.getMessage()); - return new ExceptionResponseValues(getTemplate(f, Result.ERROR), body, e); - } - - private TemplateResponseValues doFailedSearch(String message, String querytext, Format f, VitroRequest vreq) { - Map body = new HashMap(); - body.put("title", I18n.text(vreq, "search_for", querytext)); - if (StringUtils.isEmpty(message)) { - message = I18n.text(vreq, "search_failed"); - } - body.put("message", message); - return new TemplateResponseValues(getTemplate(f, Result.ERROR), body); - } - - /** - * Makes a message to display to user for a bad search term. - */ - private String makeBadSearchMessage(String querytext, String exceptionMsg, VitroRequest vreq) { - String rv = ""; - try { - // try to get the column in the search term that is causing the problems - int coli = exceptionMsg.indexOf("column"); - if (coli == -1) - return ""; - int numi = exceptionMsg.indexOf(".", coli + 7); - if (numi == -1) - return ""; - String part = exceptionMsg.substring(coli + 7, numi); - int i = Integer.parseInt(part) - 1; - - // figure out where to cut preview and post-view - int errorWindow = 5; - int pre = i - errorWindow; - if (pre < 0) - pre = 0; - int post = i + errorWindow; - if (post > querytext.length()) - post = querytext.length(); - // log.warn("pre: " + pre + " post: " + post + " term len: - // " + term.length()); - - // get part of the search term before the error and after - String before = querytext.substring(pre, i); - String after = ""; - if (post > i) - after = querytext.substring(i + 1, post); - - rv = I18n.text(vreq, "search_term_error_near") + " " + before - + "" + querytext.charAt(i) + "" + after + ""; - } catch (Throwable ex) { - return ""; - } - return rv; - } - - public static final int MAX_QUERY_LENGTH = 500; - - protected boolean isRequestedFormatXml(VitroRequest req) { - if (req != null) { - String param = req.getParameter(PARAM_XML_REQUEST); - return param != null && "1".equals(param); - } else { - return false; - } - } - - protected boolean isRequestedFormatCSV(VitroRequest req) { - if (req != null) { - String param = req.getParameter(PARAM_CSV_REQUEST); - return param != null && "1".equals(param); - } else { - return false; - } - } - - protected Format getFormat(VitroRequest req) { - if (req != null && req.getParameter("xml") != null && "1".equals(req.getParameter("xml"))) - return Format.XML; - else if (req != null && req.getParameter("csv") != null && "1".equals(req.getParameter("csv"))) - return Format.CSV; - else - return Format.HTML; - } - - protected static String getTemplate(Format format, Result result) { - if (format != null && result != null) - return templateTable.get(format).get(result); - else { - log.error("getTemplate() must not have a null format or result."); - return templateTable.get(Format.HTML).get(Result.ERROR); - } - } - - protected static Map> setupTemplateTable() { - Map> table = new HashMap<>(); - - HashMap resultsToTemplates = new HashMap(); - - // set up HTML format - resultsToTemplates.put(Result.PAGED, "extended-search-pagedResults.ftl"); - resultsToTemplates.put(Result.ERROR, "extended-search-error.ftl"); - // resultsToTemplates.put(Result.BAD_QUERY, "search-badQuery.ftl"); - table.put(Format.HTML, Collections.unmodifiableMap(resultsToTemplates)); - - // set up XML format - resultsToTemplates = new HashMap(); - resultsToTemplates.put(Result.PAGED, "extended-search-xmlResults.ftl"); - resultsToTemplates.put(Result.ERROR, "search-xmlError.ftl"); - - // resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl"); - table.put(Format.XML, Collections.unmodifiableMap(resultsToTemplates)); - - // set up CSV format - resultsToTemplates = new HashMap(); - resultsToTemplates.put(Result.PAGED, "extended-search-csvResults.ftl"); - resultsToTemplates.put(Result.ERROR, "search-csvError.ftl"); - - // resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl"); - table.put(Format.CSV, Collections.unmodifiableMap(resultsToTemplates)); - - return Collections.unmodifiableMap(table); - } + private static final String HITS_PER_PAGE_OPTIONS = "hitsPerPageOptions"; + private static final String FACETS = "facets"; + static final Log log = LogFactory.getLog(ExtendedSearchController.class); + + protected static final int DEFAULT_HITS_PER_PAGE = 30; + private static Set hitsPerPageOptions = + Stream.of(10, 30, 50).collect(Collectors.toCollection(LinkedHashSet::new)); + + protected static final int DEFAULT_MAX_HIT_COUNT = 1000; + + private static final String PARAM_XML_REQUEST = "xml"; + private static final String PARAM_CSV_REQUEST = "csv"; + private static final String PARAM_START_INDEX = "startIndex"; + private static final String PARAM_HITS_PER_PAGE = "hitsPerPage"; + private static final String PARAM_CLASSGROUP = "classgroup"; + private static final String PARAM_RDFTYPE = "type"; + public static final String PARAM_QUERY_TEXT = "querytext"; + public static final String PARAM_QUERY_SORT_BY = "sort"; + + protected static final Map> templateTable; + + protected enum Format { + HTML, + XML, + CSV; + } + + protected enum Result { + PAGED, + ERROR, + BAD_QUERY + } + + static { + templateTable = setupTemplateTable(); + } + + /** + * Overriding doGet from FreemarkerHttpController to do a page template (as opposed to body template) style output + * for XML requests. + * + * This follows the pattern in AutocompleteController.java. + */ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + VitroRequest vreq = new VitroRequest(request); + boolean wasXmlRequested = isRequestedFormatXml(vreq); + boolean wasCSVRequested = isRequestedFormatCSV(vreq); + if (!wasXmlRequested && !wasCSVRequested) { + super.doGet(vreq, response); + } else if (wasXmlRequested) { + try { + ResponseValues rvalues = processRequest(vreq); + + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/xml;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=search.xml"); + writeTemplate(rvalues.getTemplateName(), rvalues.getMap(), request, response); + } catch (Exception e) { + log.error(e, e); + } + } else if (wasCSVRequested) { + try { + ResponseValues rvalues = processRequest(vreq); + + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/csv;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=search.csv"); + writeTemplate(rvalues.getTemplateName(), rvalues.getMap(), request, response); + } catch (Exception e) { + log.error(e, e); + } + } + } + + @Override + protected ResponseValues processRequest(VitroRequest vreq) { + + // There may be other non-html formats in the future + Format format = getFormat(vreq); + boolean wasXmlRequested = Format.XML == format; + boolean wasCSVRequested = Format.CSV == format; + log.debug("Requested format was " + (wasXmlRequested ? "xml" : "html")); + boolean wasHtmlRequested = !(wasXmlRequested || wasCSVRequested); + long startTime = System.nanoTime(); + + try { + + // make sure an IndividualDao is available + if (vreq.getWebappDaoFactory() == null || vreq.getWebappDaoFactory().getIndividualDao() == null) { + log.error("Could not get webappDaoFactory or IndividualDao"); + throw new Exception("Could not access model."); + } + IndividualDao iDao = vreq.getWebappDaoFactory().getIndividualDao(); + VClassGroupDao grpDao = vreq.getWebappDaoFactory().getVClassGroupDao(); + VClassDao vclassDao = vreq.getWebappDaoFactory().getVClassDao(); + + ApplicationBean appBean = vreq.getAppBean(); + + log.debug("IndividualDao is " + iDao.toString() + " Public classes in the classgroup are " + + grpDao.getPublicGroupsWithVClasses().toString()); + log.debug("VClassDao is " + vclassDao.toString()); + + int startIndex = getStartIndex(vreq); + int hitsPerPage = getHitsPerPage(vreq); + + String queryText = getQueryText(vreq); + log.debug("Query text is \"" + queryText + "\""); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent before read filter configurations."); + } + Map filterConfigurationsByField = SearchFiltering.readFilterConfigurations(vreq); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent before get sort configurations."); + } + Map sortConfigurations = SearchFiltering.getSortConfigurations(vreq); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent before get query configurations."); + } + SearchQuery query = + getQuery(queryText, hitsPerPage, startIndex, vreq, filterConfigurationsByField, sortConfigurations); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after get query configurations."); + } + SearchEngine search = ApplicationUtils.instance().getSearchEngine(); + SearchResponse response = null; + + try { + response = search.query(query); + } catch (Exception ex) { + String msg = makeBadSearchMessage(queryText, ex.getMessage(), vreq); + log.error("could not run search query", ex); + return doFailedSearch(msg, queryText, format, vreq); + } + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after get query execution."); + } + + if (response == null) { + log.error("Search response was null"); + return doFailedSearch(I18n.text(vreq, "error_in_search_request"), queryText, format, vreq); + } + addFacetCountersFromRequest(response, filterConfigurationsByField, vreq); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after addFacetCountersFromRequest."); + } + SearchResultDocumentList docs = response.getResults(); + if (docs == null) { + log.error("Document list for a search was null"); + return doFailedSearch(I18n.text(vreq, "error_in_search_request"), queryText, format, vreq); + } + + long hitCount = docs.getNumFound(); + log.debug("Number of hits = " + hitCount); + // if ( hitCount < 1 ) { + // return doNoHits(queryText,format, vreq); + // } + + List individuals = new ArrayList(docs.size()); + for (SearchResultDocument doc : docs) { + try { + String uri = doc.getStringValue(VitroSearchTermNames.URI); + Individual ind = iDao.getIndividualByURI(uri); + if (ind != null) { + ind.setSearchSnippet(getSnippet(doc, response)); + individuals.add(ind); + } + } catch (Exception e) { + log.error("Problem getting usable individuals from search hits. ", e); + } + } + + ParamMap pagingLinkParams = new ParamMap(); + pagingLinkParams.put(PARAM_QUERY_TEXT, queryText); + pagingLinkParams.put(PARAM_HITS_PER_PAGE, String.valueOf(hitsPerPage)); + + Enumeration paramNames = vreq.getParameterNames(); + SearchFiltering.addFiltersToPageLinks(vreq, pagingLinkParams, paramNames); + + if (wasXmlRequested) { + pagingLinkParams.put(PARAM_XML_REQUEST, "1"); + } + + /* Compile the data for the templates */ + + Map body = new HashMap(); + + String classGroupParam = vreq.getParameter(PARAM_CLASSGROUP); + log.debug("ClassGroupParam is \"" + classGroupParam + "\""); + boolean classGroupFilterRequested = false; + if (!StringUtils.isBlank(classGroupParam)) { + VClassGroup grp = grpDao.getGroupByURI(classGroupParam); + classGroupFilterRequested = true; + if (grp != null && grp.getPublicName() != null) { + body.put("classGroupName", grp.getPublicName()); + } + } + + String typeParam = vreq.getParameter(PARAM_RDFTYPE); + boolean typeFilterRequested = false; + if (!StringUtils.isBlank(typeParam)) { + + VClass type = vclassDao.getVClassByURI(typeParam); + typeFilterRequested = true; + if (type != null && type.getName() != null) { + body.put("typeName", type.getName()); + } + } + + /* Add ClassGroup and type refinement links to body */ + if (wasHtmlRequested) { + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent before sorting filterConfigurationsByField values."); + } + for (Entry entry : filterConfigurationsByField.entrySet()) { + entry.getValue().sortValues(); + } + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after sorting filterConfigurationsByField values."); + } + Map filtersForTemplateById = + SearchFiltering.getFiltersForTemplate(filterConfigurationsByField); + body.put("filters", filtersForTemplateById); + body.put("filterGroups", SearchFiltering.readFilterGroupsConfigurations(vreq, filtersForTemplateById)); + body.put("sorting", sortConfigurations.values()); + body.put("emptySearch", isEmptySearchFilters(filterConfigurationsByField)); + if (!classGroupFilterRequested && !typeFilterRequested) { + // Search request includes no ClassGroup and no type, so add ClassGroup search + // refinement links. + body.put("classGroupLinks", getClassGroupsLinks(vreq, grpDao, docs, response, queryText)); + } else if (classGroupFilterRequested && !typeFilterRequested) { + // Search request is for a ClassGroup, so add rdf:type search refinement links + // but try to filter out classes that are subclasses + body.put("classLinks", getVClassLinks(vclassDao, docs, response, queryText)); + body.put("classGroupLinks", getClassGroupsLinks(vreq, grpDao, docs, response, queryText)); + + pagingLinkParams.put(PARAM_CLASSGROUP, classGroupParam); + + } else { + // search request is for a class so there are no more refinements + pagingLinkParams.put(PARAM_RDFTYPE, typeParam); + pagingLinkParams.put(PARAM_CLASSGROUP, classGroupParam); + body.put("classLinks", getVClassLinks(vclassDao, docs, response, queryText)); + body.put("classGroupLinks", getClassGroupsLinks(vreq, grpDao, docs, response, queryText)); + + } + } + + body.put("individuals", IndividualSearchResult.getIndividualTemplateModels(individuals, vreq)); + + body.put("querytext", queryText); + body.put("locale", vreq.getLocale().toLanguageTag()); + body.put("title", + new StringBuilder().append(appBean.getApplicationName()).append(" - ") + .append(I18n.text(vreq, "search_results_for")).append(" '").append(queryText).append("'") + .toString()); + + body.put("hitCount", hitCount); + body.put("startIndex", startIndex); + body.put(PARAM_HITS_PER_PAGE, hitsPerPage); + body.put(HITS_PER_PAGE_OPTIONS, hitsPerPageOptions); + + body.put("pagingLinks", + getPagingLinks(startIndex, hitsPerPage, hitCount, vreq.getServletPath(), pagingLinkParams, vreq)); + + if (startIndex != 0) { + body.put("prevPage", + getPreviousPageLink(startIndex, hitsPerPage, vreq.getServletPath(), pagingLinkParams)); + } + if (startIndex < (hitCount - hitsPerPage)) { + body.put("nextPage", getNextPageLink(startIndex, hitsPerPage, vreq.getServletPath(), pagingLinkParams)); + } + + String template = templateTable.get(format).get(Result.PAGED); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent before TemplateResponseValues."); + } + TemplateResponseValues templateResponseValues = new TemplateResponseValues(template, body); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after TemplateResponseValues."); + } + return templateResponseValues; + } catch (Throwable e) { + return doSearchError(e, format); + } + } + + private long getSpentTime(long startTime) { + return (System.nanoTime() - startTime) / 1000000; + } + + private Object isEmptySearchFilters(Map filterConfigurationsByField) { + for (SearchFilter filter : filterConfigurationsByField.values()) { + if (filter.isSelected()) { + return false; + } + } + return true; + } + + private void addFacetCountersFromRequest(SearchResponse response, Map filtersByField, + VitroRequest vreq) { + long startTime = System.nanoTime(); + List resultfacetFields = response.getFacetFields(); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after getFacetFields."); + } + Map> requestFiltersById = SearchFiltering.getRequestFilters(vreq); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after SearchFiltering.getRequestFilters."); + } + for (SearchFacetField resultField : resultfacetFields) { + SearchFilter searchFilter = filtersByField.get(resultField.getName()); + if (searchFilter == null) { + continue; + } + List values = resultField.getValues(); + + for (Count value : values) { + if (value.getCount() == 0) { + continue; + } + String valueName = value.getName(); + FilterValue filterValue = searchFilter.getValue(valueName); + if (filterValue == null) { + filterValue = new FilterValue(valueName); + searchFilter.addValue(filterValue); + } + if (requestFiltersById.containsKey(searchFilter.getId())) { + List requestedValues = requestFiltersById.get(searchFilter.getId()); + if (requestedValues.contains(valueName)) { + filterValue.setSelected(true); + } + if (!SearchFiltering.isEmptyValues(requestedValues)) { + searchFilter.setSelected(true); + } + } + if (searchFilter.isLocalizationRequired() && StringUtils.isBlank(filterValue.getName())) { + String label = SearchFiltering.getUriLabel(value.getName(), vreq); + if (!StringUtils.isBlank(label)) { + filterValue.setName(label); + } + } + // COUNT should be from the real results of the query + filterValue.setCount(value.getCount()); + } + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after SearchFacetField " + searchFilter.getName() + + "processing."); + } + } + } + + public static String getQueryText(VitroRequest vreq) { + String query = vreq.getParameter(PARAM_QUERY_TEXT); + if (StringUtils.isBlank(query)) { + return ""; + } + return query; + } + + private int getHitsPerPage(VitroRequest vreq) { + int hitsPerPage = DEFAULT_HITS_PER_PAGE; + try { + int hits = Integer.parseInt(vreq.getParameter(PARAM_HITS_PER_PAGE)); + if (hitsPerPageOptions.contains(hits)) { + hitsPerPage = hits; + } + } catch (Throwable e) { + hitsPerPage = DEFAULT_HITS_PER_PAGE; + } + log.debug("hitsPerPage is " + hitsPerPage); + return hitsPerPage; + } + + private int getStartIndex(VitroRequest vreq) { + int startIndex = 0; + try { + startIndex = Integer.parseInt(vreq.getParameter(PARAM_START_INDEX)); + } catch (Throwable e) { + startIndex = 0; + } + log.debug("startIndex is " + startIndex); + return startIndex; + } + + /** + * Get the class groups represented for the individuals in the documents. + */ + private List getClassGroupsLinks(VitroRequest vreq, VClassGroupDao grpDao, + SearchResultDocumentList docs, SearchResponse rsp, String qtxt) { + Map cgURItoCount = new HashMap(); + + List classgroups = new ArrayList(); + List ffs = rsp.getFacetFields(); + for (SearchFacetField ff : ffs) { + if (VitroSearchTermNames.CLASSGROUP_URI.equals(ff.getName())) { + List counts = ff.getValues(); + for (Count ct : counts) { + VClassGroup vcg = grpDao.getGroupByURI(ct.getName()); + if (vcg == null) { + log.debug("could not get classgroup for URI " + ct.getName()); + } else { + classgroups.add(vcg); + cgURItoCount.put(vcg.getURI(), ct.getCount()); + } + } + } + } + + grpDao.sortGroupList(classgroups); + + VClassGroupsForRequest vcgfr = VClassGroupCache.getVClassGroups(vreq); + List classGroupLinks = new ArrayList(classgroups.size()); + for (VClassGroup vcg : classgroups) { + String groupURI = vcg.getURI(); + VClassGroup localizedVcg = vcgfr.getGroup(groupURI); + long count = cgURItoCount.get(groupURI); + if (localizedVcg.getPublicName() != null && count > 0) { + classGroupLinks.add(new VClassGroupSearchLink(qtxt, localizedVcg, count)); + } + } + return classGroupLinks; + } + + private List getVClassLinks(VClassDao vclassDao, SearchResultDocumentList docs, + SearchResponse rsp, String qtxt) { + HashSet typesInHits = getVClassUrisForHits(docs); + List classes = new ArrayList(typesInHits.size()); + Map typeURItoCount = new HashMap(); + + List ffs = rsp.getFacetFields(); + for (SearchFacetField ff : ffs) { + if (VitroSearchTermNames.RDFTYPE.equals(ff.getName())) { + List counts = ff.getValues(); + for (Count ct : counts) { + String typeUri = ct.getName(); + long count = ct.getCount(); + try { + if (VitroVocabulary.OWL_THING.equals(typeUri) || count == 0) { + continue; + } + VClass type = vclassDao.getVClassByURI(typeUri); + if (type != null && !type.isAnonymous() && type.getName() != null && !"".equals(type.getName()) + && type.getGroupURI() != null) { // don't display classes that aren't in classgroups + typeURItoCount.put(typeUri, count); + classes.add(type); + } + } catch (Exception ex) { + if (log.isDebugEnabled()) + log.debug("could not add type " + typeUri, ex); + } + } + } + } + + classes.sort(new Comparator() { + public int compare(VClass o1, VClass o2) { + return o1.compareTo(o2); + } + }); + + List vClassLinks = new ArrayList(classes.size()); + for (VClass vc : classes) { + long count = typeURItoCount.get(vc.getURI()); + vClassLinks.add(new VClassSearchLink(qtxt, vc, count)); + } + + return vClassLinks; + } + + private HashSet getVClassUrisForHits(SearchResultDocumentList docs) { + HashSet typesInHits = new HashSet(); + for (SearchResultDocument doc : docs) { + try { + Collection types = doc.getFieldValues(VitroSearchTermNames.RDFTYPE); + if (types != null) { + for (Object o : types) { + String typeUri = o.toString(); + typesInHits.add(typeUri); + } + } + } catch (Exception e) { + log.error("problems getting rdf:type for search hits", e); + } + } + return typesInHits; + } + + private String getSnippet(SearchResultDocument doc, SearchResponse response) { + String docId = doc.getStringValue(VitroSearchTermNames.DOCID); + StringBuilder text = new StringBuilder(); + Map>> highlights = response.getHighlighting(); + if (highlights != null && highlights.get(docId) != null) { + List snippets = highlights.get(docId).get(VitroSearchTermNames.ALLTEXT); + if (snippets != null && snippets.size() > 0) { + text.append("... ").append(snippets.get(0)).append(" ..."); + } + } + return text.toString(); + } + + private SearchQuery getQuery(String queryText, int hitsPerPage, int startIndex, VitroRequest vreq, + Map filtersByField, Map sortOptions) { + // Lowercase the search term to support wildcard searches: The search engine + // applies no text + // processing to a wildcard search term. + if (StringUtils.isBlank(queryText)) { + queryText = "*:*"; + } + SearchQuery query = ApplicationUtils.instance().getSearchEngine().createQuery(queryText); + + query.setStart(startIndex).setRows(hitsPerPage); + + addSortRules(vreq, query, sortOptions); + + addDefaultVitroFacets(vreq, query); + + SearchFiltering.addFacetFieldsToQuery(filtersByField, query); + + Map filtersById = SearchFiltering.getFiltersById(filtersByField); + + SearchFiltering.addFiltersToQuery(vreq, query, filtersById); + + // ClassGroup filtering param + String classgroupParam = vreq.getParameter(PARAM_CLASSGROUP); + + // rdf:type filtering param + String typeParam = vreq.getParameter(PARAM_RDFTYPE); + + if (!StringUtils.isBlank(classgroupParam)) { + // ClassGroup filtering + log.debug("Firing classgroup query "); + log.debug("request.getParameter(classgroup) is " + classgroupParam); + query.addFilterQuery(VitroSearchTermNames.CLASSGROUP_URI + ":\"" + classgroupParam + "\""); + + // with ClassGroup filtering we want type facets + query.addFacetFields(VitroSearchTermNames.RDFTYPE).setFacetLimit(1000); + + } else if (!StringUtils.isBlank(typeParam)) { + // rdf:type filtering + log.debug("Firing type query "); + log.debug("request.getParameter(type) is " + typeParam); + query.addFilterQuery(VitroSearchTermNames.RDFTYPE + ":\"" + typeParam + "\""); + // with type filtering we don't have facets. + } else { + // When no filtering is set, we want ClassGroup facets + query.addFacetFields(VitroSearchTermNames.CLASSGROUP_URI).setFacetLimit(1000); + } + + log.debug("Query = " + query.toString()); + return query; + } + + private void addDefaultVitroFacets(VitroRequest vreq, SearchQuery query) { + String[] facets = vreq.getParameterValues(FACETS); + if (facets != null && facets.length > 0) { + query.addFacetFields(facets); + } + } + + private void addSortRules(VitroRequest vreq, SearchQuery query, Map sortOptions) { + String sortType = getSortType(vreq); + if (!StringUtils.isBlank(sortType) && sortOptions.containsKey(sortType)) { + SortConfiguration conf = sortOptions.get(sortType); + query.addSortField(conf.getField(vreq.getLocale()), conf.getSortOrder()); + conf.setSelected(true); + } + } + + private String getSortType(VitroRequest vreq) { + return vreq.getParameter(PARAM_QUERY_SORT_BY); + } + + public static class VClassGroupSearchLink extends LinkTemplateModel { + private static final String EXTENDEDSEARCH = "/extendedsearch"; + long count = 0; + + VClassGroupSearchLink(String querytext, VClassGroup classgroup, long count) { + super(classgroup.getPublicName(), EXTENDEDSEARCH, PARAM_QUERY_TEXT, querytext, PARAM_CLASSGROUP, + classgroup.getURI()); + this.count = count; + } + + public String getCount() { + return Long.toString(count); + } + } + + public static class VClassSearchLink extends LinkTemplateModel { + long count = 0; + + VClassSearchLink(String querytext, VClass type, long count) { + super(type.getName(), "/extendedsearch", PARAM_QUERY_TEXT, querytext, PARAM_RDFTYPE, type.getURI()); + this.count = count; + } + + public String getCount() { + return Long.toString(count); + } + } + + protected static List getPagingLinks(int startIndex, int hitsPerPage, long hitCount, String baseUrl, + ParamMap params, VitroRequest vreq) { + + List pagingLinks = new ArrayList(); + + // No paging links if only one page of results + if (hitCount <= hitsPerPage) { + return pagingLinks; + } + + int maxHitCount = DEFAULT_MAX_HIT_COUNT; + if (startIndex >= DEFAULT_MAX_HIT_COUNT - hitsPerPage) { + maxHitCount = startIndex + DEFAULT_MAX_HIT_COUNT; + } + + for (int i = 0; i < hitCount; i += hitsPerPage) { + params.put(PARAM_START_INDEX, String.valueOf(i)); + if (i < maxHitCount - hitsPerPage) { + int pageNumber = i / hitsPerPage + 1; + boolean iIsCurrentPage = (i >= startIndex && i < (startIndex + hitsPerPage)); + if (iIsCurrentPage) { + pagingLinks.add(new PagingLink(pageNumber)); + } else { + pagingLinks.add(new PagingLink(pageNumber, baseUrl, params)); + } + } else { + pagingLinks.add(new PagingLink(I18n.text(vreq, "paging_link_more"), baseUrl, params)); + break; + } + } + + return pagingLinks; + } + + private String getPreviousPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) { + params.put(PARAM_START_INDEX, String.valueOf(startIndex - hitsPerPage)); + return UrlBuilder.getUrl(baseUrl, params); + } + + private String getNextPageLink(int startIndex, int hitsPerPage, String baseUrl, ParamMap params) { + params.put(PARAM_START_INDEX, String.valueOf(startIndex + hitsPerPage)); + return UrlBuilder.getUrl(baseUrl, params); + } + + protected static class PagingLink extends LinkTemplateModel { + + PagingLink(int pageNumber, String baseUrl, ParamMap params) { + super(String.valueOf(pageNumber), baseUrl, params); + } + + // Constructor for current page item: not a link, so no url value. + PagingLink(int pageNumber) { + setText(String.valueOf(pageNumber)); + } + + // Constructor for "more..." item + PagingLink(String text, String baseUrl, ParamMap params) { + super(text, baseUrl, params); + } + } + + private ExceptionResponseValues doSearchError(Throwable e, Format f) { + Map body = new HashMap(); + body.put("message", "Search failed: " + e.getMessage()); + return new ExceptionResponseValues(getTemplate(f, Result.ERROR), body, e); + } + + private TemplateResponseValues doFailedSearch(String message, String querytext, Format f, VitroRequest vreq) { + Map body = new HashMap(); + body.put("title", I18n.text(vreq, "search_for", querytext)); + if (StringUtils.isEmpty(message)) { + message = I18n.text(vreq, "search_failed"); + } + body.put("message", message); + return new TemplateResponseValues(getTemplate(f, Result.ERROR), body); + } + + /** + * Makes a message to display to user for a bad search term. + */ + private String makeBadSearchMessage(String querytext, String exceptionMsg, VitroRequest vreq) { + String rv = ""; + try { + // try to get the column in the search term that is causing the problems + int coli = exceptionMsg.indexOf("column"); + if (coli == -1) { + return ""; + } + int numi = exceptionMsg.indexOf(".", coli + 7); + if (numi == -1) { + return ""; + } + String part = exceptionMsg.substring(coli + 7, numi); + int i = Integer.parseInt(part) - 1; + + // figure out where to cut preview and post-view + int errorWindow = 5; + int pre = i - errorWindow; + if (pre < 0) { + pre = 0; + } + int post = i + errorWindow; + if (post > querytext.length()) { + post = querytext.length(); + } + // log.warn("pre: " + pre + " post: " + post + " term len: + // " + term.length()); + + // get part of the search term before the error and after + String before = querytext.substring(pre, i); + String after = ""; + if (post > i) { + after = querytext.substring(i + 1, post); + } + rv = I18n.text(vreq, "search_term_error_near") + " " + before + + "" + querytext.charAt(i) + "" + after + ""; + } catch (Throwable ex) { + return ""; + } + return rv; + } + + public static final int MAX_QUERY_LENGTH = 500; + + protected boolean isRequestedFormatXml(VitroRequest req) { + if (req != null) { + String param = req.getParameter(PARAM_XML_REQUEST); + return param != null && "1".equals(param); + } else { + return false; + } + } + + protected boolean isRequestedFormatCSV(VitroRequest req) { + if (req != null) { + String param = req.getParameter(PARAM_CSV_REQUEST); + return param != null && "1".equals(param); + } else { + return false; + } + } + + protected Format getFormat(VitroRequest req) { + if (req != null && req.getParameter("xml") != null && "1".equals(req.getParameter("xml"))) { + return Format.XML; + } else if (req != null && req.getParameter("csv") != null && "1".equals(req.getParameter("csv"))) { + return Format.CSV; + } else { + return Format.HTML; + } + + } + + protected static String getTemplate(Format format, Result result) { + if (format != null && result != null) { + return templateTable.get(format).get(result); + } else { + log.error("getTemplate() must not have a null format or result."); + return templateTable.get(Format.HTML).get(Result.ERROR); + } + } + + protected static Map> setupTemplateTable() { + Map> table = new HashMap<>(); + + HashMap resultsToTemplates = new HashMap(); + + // set up HTML format + resultsToTemplates.put(Result.PAGED, "extended-search-pagedResults.ftl"); + resultsToTemplates.put(Result.ERROR, "extended-search-error.ftl"); + // resultsToTemplates.put(Result.BAD_QUERY, "search-badQuery.ftl"); + table.put(Format.HTML, Collections.unmodifiableMap(resultsToTemplates)); + + // set up XML format + resultsToTemplates = new HashMap(); + resultsToTemplates.put(Result.PAGED, "extended-search-xmlResults.ftl"); + resultsToTemplates.put(Result.ERROR, "search-xmlError.ftl"); + + // resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl"); + table.put(Format.XML, Collections.unmodifiableMap(resultsToTemplates)); + + // set up CSV format + resultsToTemplates = new HashMap(); + resultsToTemplates.put(Result.PAGED, "extended-search-csvResults.ftl"); + resultsToTemplates.put(Result.ERROR, "search-csvError.ftl"); + + // resultsToTemplates.put(Result.BAD_QUERY, "search-xmlBadQuery.ftl"); + table.put(Format.CSV, Collections.unmodifiableMap(resultsToTemplates)); + + return Collections.unmodifiableMap(table); + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilter.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilter.java index 1a69851b21..3ea36e8937 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilter.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilter.java @@ -15,327 +15,323 @@ import org.apache.commons.lang3.StringUtils; import org.apache.jena.rdf.model.RDFNode; - - public class SearchFilter { private static final String FILTER = "Filter"; - private static final String RANGE_FILTER = "RangeFilter"; - - private String id; - private String name = ""; - private String from = ""; - private String to = ""; - private String fromYear = ""; - private String toYear = ""; - private boolean isPublic = false; - - private String min = "0"; - private String max = "2000"; - private int moreLimit = 30; - private int order = 0; - private String field= ""; - private String endField= ""; - private String inputText= ""; - private boolean localizationRequired = false; - private boolean multivalued = false; - private boolean selected = false; - private boolean input = false; - private Map values = new LinkedHashMap<>(); - - private boolean inputRegex = false; - - private boolean facetsRequired; - - private String type = FILTER; - private String rangeText = ""; - private String rangeInput = ""; - private boolean hidden = false; - - public String getRangeInput() { - return rangeInput; - } - - public void setRangeInput(String range) { - this.rangeInput = range; - } - - public String getRangeText() { - return rangeText; - } - - public SearchFilter(String id){ - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(RDFNode rdfNode) { - if (rdfNode != null) { - name = rdfNode.asLiteral().getLexicalForm().trim(); - } - } - - public void setOrder(RDFNode rdfNode) { - if (rdfNode != null) { - order = rdfNode.asLiteral().getInt(); - } - } - - public Integer getOrder() { - return order; - } - - public String getField() { - return field; - } - - public String getEndField() { - return endField; - } - - public void setEndField(String endField) { - this.endField = endField; - } - - public void addValue(FilterValue value) { - values.put(value.getId(),value); - } - - public FilterValue getValue(String name) { - return values.get(name); - } - - public Map getValues() { - return values; - } - - public void setField(String fieldName) { - field = fieldName; - } - - public boolean contains(String valueId) { - if (values.containsKey(valueId)) { - return true; - } - return false; - } - - public boolean isLocalizationRequired() { - return localizationRequired; - } - - public void setLocalizationRequired(boolean localizationRequired) { - this.localizationRequired = localizationRequired; - } - - public String getId() { - return id; - } - - public boolean isSelected() { - return selected; - } - - public void setSelected(boolean isSelected) { - this.selected = isSelected; - } - - - public boolean isMultivalued() { - return multivalued; - } - - - public void setMultivalued(boolean multivalued) { - this.multivalued = multivalued; - } - - public boolean isInput() { - return input; - } - - public boolean isRange() { - return RANGE_FILTER.equals(type); - } - - public void setInput(boolean input) { - this.input = input; - } - - public String getInputText() { - return inputText; - } - - public void setInputText(String inputText) { - if (StringUtils.isBlank(inputText)) { - return; - } - selected = true; - this.inputText = inputText; - } - - public void setInputRegex(boolean regex) { - this.inputRegex = regex; - } - - public boolean isInputRegex() { - return inputRegex; - } - - public void setFacetsRequired(boolean facetsRequired) { - this.facetsRequired = facetsRequired; - } - - public boolean isFacetsRequired() { - return facetsRequired; - } - - public void setType(RDFNode rdfNode) { - String typeOntClass = rdfNode.toString(); - if (typeOntClass.contains(RANGE_FILTER)) { - type = RANGE_FILTER; - } - } - - public String getType() { - return type; - } - - public String getFrom() { - return from; - } - - public void setFrom(String fromValue) { - this.from = fromValue; - } - - public String getTo() { - return to; - } - - public void setTo(String toValue) { - this.to = toValue; - } - - public void setRangeValues(String filterRangeText) { - if (StringUtils.isBlank(filterRangeText)) { return; } - this.rangeInput = filterRangeText; - String[] dates = filterRangeText.trim().split(" "); - if (dates.length != 2) { - return; - } - setFrom(dates[0]); - setFromYear(dates[0]); - setTo(to = dates[1]); - setToYear(dates[1]); - rangeText = "[" + from.trim() + " TO " + to.trim() + "]"; - selected = true; - } - - public String getMin() { - return min; - } - - public void setMin(String min) { - this.min = min; - } - - public String getMax() { - return max; - } - - public void setMax(String max) { - this.max = max; - } - - private String getYear(String timeString) { - Instant time = Instant.parse ( timeString ); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy").withZone(ZoneId.systemDefault()) ; - String formatted = formatter.format(time); - return formatted; - } - - public String getFromYear() { - return fromYear; - } - - public void setFromYear(String fromYear) { - this.fromYear = getYear(fromYear); - } - - public String getToYear() { - return toYear; - } - - public void setToYear(String toYear) { - this.toYear = getYear(toYear); - } - - - public void sortValues() { - List> list = new LinkedList<>(values.entrySet()); - list.sort(new FilterValueComparator()); - values = list.stream() - .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new)); - } - - public boolean isPublic() { - return isPublic; - } - - public void setPublic(boolean isPublic) { - this.isPublic = isPublic; - } - - private class FilterValueComparator implements Comparator>{ - public int compare(Entry obj1, Entry obj2) { - FilterValue filter1 = obj1.getValue(); - FilterValue filter2 = obj2.getValue(); - int result = filter1.getOrder().compareTo(filter2.getOrder()); - if (result == 0) { - // order are equal, sort by name - return filter1.getName().toLowerCase().compareTo(filter2.getName().toLowerCase()); - } - else { - return result; - } - } - } - - public void removeValuesWithZeroCount() { - Iterator> iterator = values.entrySet().iterator(); - while (iterator.hasNext()) { - Entry entry = iterator.next(); - FilterValue value = entry.getValue(); - if (value.getCount() == 0) { - iterator.remove(); - } - } - } - - public boolean isEmpty() { - if (values.size() > 0 || isInput() || isRange()) { - return false; - } - return true; - } - - public void setHidden(boolean b) { - this.hidden = b; - } - - public boolean isHidden() { - return hidden; - } - - public int getMoreLimit() { - return moreLimit; - } - - public void setMoreLimit(int moreLimit) { - this.moreLimit = moreLimit; - } + private static final String RANGE_FILTER = "RangeFilter"; + + private String id; + private String name = ""; + private String from = ""; + private String to = ""; + private String fromYear = ""; + private String toYear = ""; + private boolean isPublic = false; + + private String min = "0"; + private String max = "2000"; + private int moreLimit = 30; + private int order = 0; + private String field = ""; + private String endField = ""; + private String inputText = ""; + private boolean localizationRequired = false; + private boolean multivalued = false; + private boolean selected = false; + private boolean input = false; + private Map values = new LinkedHashMap<>(); + + private boolean inputRegex = false; + + private boolean facetsRequired; + + private String type = FILTER; + private String rangeText = ""; + private String rangeInput = ""; + private boolean hidden = false; + + public String getRangeInput() { + return rangeInput; + } + + public void setRangeInput(String range) { + this.rangeInput = range; + } + + public String getRangeText() { + return rangeText; + } + + public SearchFilter(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(RDFNode rdfNode) { + if (rdfNode != null) { + name = rdfNode.asLiteral().getLexicalForm().trim(); + } + } + + public void setOrder(RDFNode rdfNode) { + if (rdfNode != null) { + order = rdfNode.asLiteral().getInt(); + } + } + + public Integer getOrder() { + return order; + } + + public String getField() { + return field; + } + + public String getEndField() { + return endField; + } + + public void setEndField(String endField) { + this.endField = endField; + } + + public void addValue(FilterValue value) { + values.put(value.getId(), value); + } + + public FilterValue getValue(String name) { + return values.get(name); + } + + public Map getValues() { + return values; + } + + public void setField(String fieldName) { + field = fieldName; + } + + public boolean contains(String valueId) { + if (values.containsKey(valueId)) { + return true; + } + return false; + } + + public boolean isLocalizationRequired() { + return localizationRequired; + } + + public void setLocalizationRequired(boolean localizationRequired) { + this.localizationRequired = localizationRequired; + } + + public String getId() { + return id; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean isSelected) { + this.selected = isSelected; + } + + public boolean isMultivalued() { + return multivalued; + } + + public void setMultivalued(boolean multivalued) { + this.multivalued = multivalued; + } + + public boolean isInput() { + return input; + } + + public boolean isRange() { + return RANGE_FILTER.equals(type); + } + + public void setInput(boolean input) { + this.input = input; + } + + public String getInputText() { + return inputText; + } + + public void setInputText(String inputText) { + if (StringUtils.isBlank(inputText)) { + return; + } + selected = true; + this.inputText = inputText; + } + + public void setInputRegex(boolean regex) { + this.inputRegex = regex; + } + + public boolean isInputRegex() { + return inputRegex; + } + + public void setFacetsRequired(boolean facetsRequired) { + this.facetsRequired = facetsRequired; + } + + public boolean isFacetsRequired() { + return facetsRequired; + } + + public void setType(RDFNode rdfNode) { + String typeOntClass = rdfNode.toString(); + if (typeOntClass.contains(RANGE_FILTER)) { + type = RANGE_FILTER; + } + } + + public String getType() { + return type; + } + + public String getFrom() { + return from; + } + + public void setFrom(String fromValue) { + this.from = fromValue; + } + + public String getTo() { + return to; + } + + public void setTo(String toValue) { + this.to = toValue; + } + + public void setRangeValues(String filterRangeText) { + if (StringUtils.isBlank(filterRangeText)) { + return; + } + this.rangeInput = filterRangeText; + String[] dates = filterRangeText.trim().split(" "); + if (dates.length != 2) { + return; + } + setFrom(dates[0]); + setFromYear(dates[0]); + setTo(to = dates[1]); + setToYear(dates[1]); + rangeText = "[" + from.trim() + " TO " + to.trim() + "]"; + selected = true; + } + + public String getMin() { + return min; + } + + public void setMin(String min) { + this.min = min; + } + + public String getMax() { + return max; + } + + public void setMax(String max) { + this.max = max; + } + + private String getYear(String timeString) { + Instant time = Instant.parse(timeString); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy").withZone(ZoneId.systemDefault()); + String formatted = formatter.format(time); + return formatted; + } + + public String getFromYear() { + return fromYear; + } + + public void setFromYear(String fromYear) { + this.fromYear = getYear(fromYear); + } + + public String getToYear() { + return toYear; + } + + public void setToYear(String toYear) { + this.toYear = getYear(toYear); + } + + public void sortValues() { + List> list = new LinkedList<>(values.entrySet()); + list.sort(new FilterValueComparator()); + values = list.stream() + .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new)); + } + + public boolean isPublic() { + return isPublic; + } + + public void setPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + private class FilterValueComparator implements Comparator> { + public int compare(Entry obj1, Entry obj2) { + FilterValue filter1 = obj1.getValue(); + FilterValue filter2 = obj2.getValue(); + int result = filter1.getOrder().compareTo(filter2.getOrder()); + if (result == 0) { + // order are equal, sort by name + return filter1.getName().toLowerCase().compareTo(filter2.getName().toLowerCase()); + } else { + return result; + } + } + } + + public void removeValuesWithZeroCount() { + Iterator> iterator = values.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + FilterValue value = entry.getValue(); + if (value.getCount() == 0) { + iterator.remove(); + } + } + } + + public boolean isEmpty() { + if (values.size() > 0 || isInput() || isRange()) { + return false; + } + return true; + } + + public void setHidden(boolean b) { + this.hidden = b; + } + + public boolean isHidden() { + return hidden; + } + + public int getMoreLimit() { + return moreLimit; + } + + public void setMoreLimit(int moreLimit) { + this.moreLimit = moreLimit; + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilterGroup.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilterGroup.java index 9ea4b413d3..44451384fc 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilterGroup.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFilterGroup.java @@ -4,62 +4,62 @@ public class SearchFilterGroup { - private String id; - private String label; - private boolean isPublic = false; - private boolean hidden = true; - - private LinkedHashSet filters = new LinkedHashSet <>(); - - public LinkedHashSet getFilters() { - return filters; - } - - public void setFilters(LinkedHashSet filters) { - this.filters = filters; - } - - public SearchFilterGroup(String groupId, String groupLabel) { - this.setId(groupId); - this.setLabel(groupLabel); - } - - public void addFilterId(String filterId) { - if(!filters.contains(filterId)) { - filters.add(filterId); - } - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public boolean isPublic() { - return isPublic; - } - - public void setPublic(boolean isPublic) { - this.isPublic = isPublic; - } - - public boolean isHidden() { - return hidden; - } - - public void setHidden(boolean hidden) { - this.hidden = hidden; - } + private String id; + private String label; + private boolean isPublic = false; + private boolean hidden = true; + + private LinkedHashSet filters = new LinkedHashSet<>(); + + public LinkedHashSet getFilters() { + return filters; + } + + public void setFilters(LinkedHashSet filters) { + this.filters = filters; + } + + public SearchFilterGroup(String groupId, String groupLabel) { + this.setId(groupId); + this.setLabel(groupLabel); + } + + public void addFilterId(String filterId) { + if (!filters.contains(filterId)) { + filters.add(filterId); + } + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public boolean isPublic() { + return isPublic; + } + + public void setPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + public boolean isHidden() { + return hidden; + } + + public void setHidden(boolean hidden) { + this.hidden = hidden; + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFiltering.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFiltering.java index e5d97f218d..fbee90765a 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFiltering.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SearchFiltering.java @@ -13,6 +13,12 @@ import java.util.function.Function; import java.util.stream.Collectors; +import edu.cornell.mannlib.vedit.beans.LoginStatusBean; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -29,560 +35,548 @@ import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.shared.Lock; -import edu.cornell.mannlib.vedit.beans.LoginStatusBean; -import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap; -import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; -import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; -import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; - public class SearchFiltering { - static final Log log = LogFactory.getLog(SearchFiltering.class); - - private static final String FILTER_RANGE = "filter_range_"; - private static final String FILTER_INPUT_PREFIX = "filter_input_"; - private static final String FILTERS = "filters"; - - private static final String FILTER_QUERY = " PREFIX vitro: \n" - + " PREFIX vitro: \n" - + " PREFIX search: \n" - + " PREFIX gesah: \n" - + " PREFIX rdf: \n" - + " PREFIX rdfs: \n" - + "SELECT ?filter_id ?filter_type ?filter_label ?value_label ?value_id ?field_name ?public ?filter_order ?value_order (STR(?isUriReq) as ?isUri ) ?multivalued ?input ?regex ?facet ?min ?max ?public_default ?value_public ?more_limit \n" - + " WHERE {\n" - + " ?filter rdf:type search:Filter .\n" - + " ?filter rdfs:label ?filter_label .\n" - + " ?filter search:id ?filter_id .\n" - + " ?filter ?filter_type .\n" - + " ?filter search:filterField ?field .\n" - + " ?field search:indexField ?field_name .\n" - + " OPTIONAL {?filter search:hasKnownValue ?value . \n" - + " ?value rdfs:label ?value_label .\n" - + " ?value search:id ?value_id .\n" - + " OPTIONAL {" - + " ?value search:order ?v_order .\n" - + " bind(?v_order as ?value_order_found).\n" - + " }" - + " OPTIONAL {" - + " ?value search:defaultPublic ?public_default ." - + " }" - + " OPTIONAL {" - + " ?value search:public ?value_public ." - + " }" - + " }\n" - + " OPTIONAL {?field search:multivalued ?multivalued}\n" - + " OPTIONAL {?filter search:isUriValues ?isUriReq }\n" - + " OPTIONAL {?filter search:userInput ?input }\n" - + " OPTIONAL {?filter search:userInputRegex ?regex }\n" - + " OPTIONAL {?filter search:facetResults ?facet }\n" - + " OPTIONAL {?filter search:from ?min }\n" - + " OPTIONAL {?filter search:public ?public }\n" - + " OPTIONAL {?filter search:to ?max }\n" - + " OPTIONAL {?filter search:moreLimit ?more_limit }\n" - + " OPTIONAL {\n" - + " ?filter search:order ?f_order \n" - + " bind(?f_order as ?filter_order_found).\n" - + " }\n" - + " BIND(coalesce(?filter_order_found, 0) as ?filter_order)\n" - + " BIND(coalesce(?value_order_found, 0) as ?value_order)\n" - + "\n" - + " } ORDER BY ?filter_id ?filter_order ?value_order"; - - private static final String FILTER_GROUPS_QUERY = "" - + "PREFIX rdf: \n" - + " PREFIX search: \n" - + " PREFIX gesah: \n" - + " PREFIX rdfs: \n" - + "SELECT ?group_id (STR(?group_l) AS ?group_label) ?filter_id ?order ?filter_order ?public\n" - + " WHERE {\n" - + " ?filter_group rdf:type search:FilterGroup .\n" - + " ?filter_group search:contains ?filter .\n" - + " ?filter_group rdfs:label ?group_l .\n" - + " ?filter_group search:id ?group_id .\n" - + " OPTIONAL { ?filter_group search:order ?order . } \n" - + " ?filter search:id ?filter_id .\n" - + " OPTIONAL {?filter_group search:public ?public }\n" - + " OPTIONAL{ ?filter search:order ?f_order .\n" - + " bind(?f_order as ?filter_order_found).\n" - + " }\n" - + " BIND(coalesce(?filter_order_found, 0) as ?filter_order)\n" - + " } ORDER BY ?order ?group_label ?filter_order"; - - static final String LABEL_QUERY = "" - + " PREFIX rdfs: \n" - + " SELECT ?label\n" + " WHERE {" - + "\n" + " ?uri rdfs:label ?label .\n" - + "} LIMIT 1"; - - private static final String SORT_QUERY = "" - + " PREFIX rdf: \n" - + " PREFIX rdfs: \n" - + " PREFIX gesah: \n" - + " Prefix search: \n" - + " SELECT ( STR(?sort_label) as ?label ) ?id ?searchField ?multilingual ?isAsc ?sort_order \n" - + " WHERE {\n" - + " ?sort rdf:type search:Sort . \n" - + " ?sort rdfs:label ?sort_label .\n" - + " ?sort search:sortField ?field .\n" - + " ?sort search:id ?id .\n" - + " ?field search:indexField ?searchField .\n" - + " OPTIONAL {\n" - + " ?field search:isLanguageSpecific ?f_multilingual .\n" - + " BIND(?f_multilingual as ?bind_multilingual) .\n" - + " }\n" - + " OPTIONAL {\n" - + " ?sort search:isAscending ?f_ord .\n" - + " BIND(?f_ord as ?f_order) .\n" - + " }\n" - + " OPTIONAL{ " - + " ?sort search:order ?s_order .\n" - + " bind(?s_order as ?sort_order_found).\n" - + " }\n" - + " BIND(coalesce(?sort_order_found, 0) as ?sort_order)\n" - + " BIND(COALESCE(?f_order, false) as ?isAsc)\n" - + " BIND(COALESCE(?bind_multilingual, false) as ?multilingual)\n" - + " } ORDER BY ?sort_order ?label "; - - static void addFiltersToQuery(VitroRequest vreq, SearchQuery query, Map filterById) { - Enumeration paramNames = vreq.getParameterNames(); - while (paramNames.hasMoreElements()) { - String paramFilterName = paramNames.nextElement(); - if (!StringUtils.isBlank(paramFilterName) && paramFilterName.startsWith(SearchFiltering.FILTERS)) { - String[] filters = vreq.getParameterValues(paramFilterName); - if (filters != null && filters.length > 0) { - for (String filter : filters) { - String[] pair = filter.split(":", 2); - if (pair.length == 2) { - String name = pair[0].replace("\"", ""); - String value = pair[1].replace("\"", ""); - SearchFilter searchFilter = filterById.get(name); - if (searchFilter != null && searchFilter.getField() != null) { - query.addFilterQuery(searchFilter.getField() + ":\"" + value + "\""); - } - - } - } - } - - } - } - for (String filterId : filterById.keySet()) { - SearchFilter searchFilter = filterById.get(filterId); - if (searchFilter.isInput()) { - SearchFiltering.addInputFilter(query, searchFilter); - } else if (searchFilter.isRange()) { - SearchFiltering.addRangeFilter(query, searchFilter); - } - for (FilterValue fv : searchFilter.getValues().values()) { - if (fv.isDefaultPublic()) { - query.addFilterQuery(searchFilter.getField() + ":\"" + fv.getId() + "\""); - } - } - } - } - - private static void addRangeFilter(SearchQuery query, SearchFilter searchFilter) { - String rangeText = searchFilter.getRangeText(); - if (StringUtils.isBlank(rangeText)) { - return; - } - query.addFilterQuery(searchFilter.getField() + ":\"" + rangeText + "\""); - } - - private static void addInputFilter(SearchQuery query, SearchFilter searchFilter) { - if (StringUtils.isBlank(searchFilter.getInputText()) || - ExtendedSearchController.PARAM_QUERY_TEXT.equals(searchFilter.getId())) { - return; - } - String searchText = searchFilter.getInputText(); - if (searchFilter.isInputRegex()) { - searchText = searchText.replaceAll("([ )(:])", "\\\\$1") + "*"; - query.addFilterQuery(searchFilter.getField() + ":" + searchText); - } else { - query.addFilterQuery(searchFilter.getField() + ":\"" + searchText + "\""); - } - } - - static Map> getRequestFilters(VitroRequest vreq) { - Map> requestFilters = new HashMap<>(); - Enumeration paramNames = vreq.getParameterNames(); - while (paramNames.hasMoreElements()) { - String paramFilterName = paramNames.nextElement(); - if (!StringUtils.isBlank(paramFilterName) && paramFilterName.startsWith(SearchFiltering.FILTERS)) { - String[] filters = vreq.getParameterValues(paramFilterName); - if (filters != null && filters.length > 0) { - for (String filter : filters) { - String[] pair = filter.split(":", 2); - if (pair.length == 2) { - String name = pair[0].replace("\"", ""); - String value = pair[1].replace("\"", ""); - if (requestFilters.containsKey(name)) { - List list = requestFilters.get(name); - list.add(value); - } else { - requestFilters.put(name, new LinkedList(Arrays.asList(value))); - } - } - } - } - } - } - - return requestFilters; - } - - private static String getFilterInputText(VitroRequest vreq, String name) { - if (ExtendedSearchController.PARAM_QUERY_TEXT.equals(name)) { - return ExtendedSearchController.getQueryText(vreq); - } - String[] values = vreq.getParameterValues(SearchFiltering.FILTER_INPUT_PREFIX + name); - if (values != null && values.length > 0) { - return values[0]; - } - return ""; - } - - private static String getFilterRangeText(VitroRequest vreq, String name) { - String[] values = vreq.getParameterValues(SearchFiltering.FILTER_RANGE + name); - if (values != null && values.length > 0) { - return values[0]; - } - return ""; - } - - static void setSelectedFilters(Map filtersByField, Map> requestFilters) { - for (SearchFilter filter : filtersByField.values()) { - if (requestFilters.containsKey(filter.getId())) { - List requestValues = requestFilters.get(filter.getId()); - if (!SearchFiltering.isEmptyValues(requestValues)) { - filter.setSelected(true); - for (String requestValue : requestValues) { - if (filter.getValues().containsKey(requestValue)) { - FilterValue value = filter.getValue(requestValue); - value.setSelected(true); - } - } - } - } - } - } - - static Map readFilterConfigurations(VitroRequest vreq) { - long startTime = System.nanoTime(); - - Map filtersByField = new LinkedHashMap<>(); - Model model = ModelAccess.on(vreq).getOntModelSelector().getABoxModel(); - model.enterCriticalSection(Lock.READ); - try { - Query facetQuery = QueryFactory.create(FILTER_QUERY); - QueryExecution qexec = QueryExecutionFactory.create(facetQuery, model); - ResultSet results = qexec.execSelect(); - while (results.hasNext()) { - QuerySolution solution = results.nextSolution(); - if (solution.get("filter_id") == null || solution.get("field_name") == null - || solution.get("filter_type") == null) { - continue; - } - String resultFilterId = solution.get("filter_id").toString(); - String resultFieldName = solution.get("field_name").toString(); - - SearchFilter filter = null; - if (filtersByField.containsKey(resultFieldName)) { - filter = filtersByField.get(resultFieldName); - } else { - filter = createSearchFilter(vreq, filtersByField, solution, resultFilterId, resultFieldName); - } - if (solution.get("value_id") == null) { - continue; - } - String valueId = solution.get("value_id").toString(); - if (!filter.contains(valueId)) { - FilterValue value = new FilterValue(valueId); - value.setName(solution.get("value_label")); - value.setOrder(solution.get("value_order")); - value.setPubliclyAvailable(solution.get("value_public")); - filter.addValue(value); - RDFNode pubDefault = solution.get("public_default"); - if (pubDefault != null && pubDefault.asLiteral().getBoolean() && isNotLoggedIn(vreq)){ - value.setDefaultPublic(true); - } - } - } - } finally { - model.leaveCriticalSection(); - } - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after FILTER QUERY request."); - } - Map> requestFilters = getRequestFilters(vreq); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after getRequestFilters."); - } - setSelectedFilters(filtersByField, requestFilters); - if (log.isDebugEnabled()) { - log.debug( getSpentTime(startTime) + "ms spent after setSelectedFilters."); - } - return sortFilters(filtersByField); - } - - private static boolean isNotLoggedIn(VitroRequest vreq) { - UserAccount user = LoginStatusBean.getCurrentUser(vreq); - return user == null; - } - - public static List readFilterGroupsConfigurations(VitroRequest vreq, Map filtersById) { - Map groups = new LinkedHashMap<>(); - Model model = ModelAccess.on(vreq).getOntModelSelector().getABoxModel(); - model.enterCriticalSection(Lock.READ); - try { - Query facetQuery = QueryFactory.create(FILTER_GROUPS_QUERY); - QueryExecution qexec = QueryExecutionFactory.create(facetQuery, model); - ResultSet results = qexec.execSelect(); - while (results.hasNext()) { - QuerySolution solution = results.nextSolution(); - if (solution.get("filter_id") == null || - solution.get("group_label") == null || - solution.get("group_id") == null) { - continue; - } - String filterId = solution.get("filter_id").toString(); - String groupId = solution.get("group_id").toString(); - String groupLabel = solution.get("group_label").toString(); - SearchFilterGroup group = null; - if (groups.containsKey(groupId)) { - group = groups.get(groupId); - } else { - group = new SearchFilterGroup(groupId, groupLabel); - RDFNode publicNode = solution.get("public"); - if (publicNode != null) { - group.setPublic(publicNode.asLiteral().getBoolean()); - } - groups.put(groupId, group); - } - group.addFilterId(filterId); - SearchFilter filter = filtersById.get(filterId); - if (filter != null && !filter.isHidden()) { - group.setHidden(false); - } - } - } finally { - model.leaveCriticalSection(); - } - return new LinkedList(groups.values()); - } - - public static Map getSortConfigurations(VitroRequest vreq) { - Map sortConfigurations = new LinkedHashMap<>(); - Model model = ModelAccess.on(vreq).getOntModelSelector().getABoxModel(); - model.enterCriticalSection(Lock.READ); - try { - Query facetQuery = QueryFactory.create(SORT_QUERY); - QueryExecution qexec = QueryExecutionFactory.create(facetQuery, model); - ResultSet results = qexec.execSelect(); - while (results.hasNext()) { - QuerySolution solution = results.nextSolution(); - if (solution.get("label") == null || - solution.get("id") == null || - solution.get("searchField") == null) { - continue; - } - - String field = solution.get("searchField").toString(); - String id = solution.get("id").toString(); - String label = solution.get("label").toString(); - - SortConfiguration config = null; - if (sortConfigurations.containsKey(id)) { - config = sortConfigurations.get(id); - } else { - config = new SortConfiguration(id, label, field); - - RDFNode multilingual = solution.get("multilingual"); - if (multilingual != null) { - config.setMultilingual(multilingual.asLiteral().getBoolean()); - } - - RDFNode isAsc = solution.get("isAsc"); - if (isAsc != null) { - config.setAscOrder(isAsc.asLiteral().getBoolean()); - } - - RDFNode order = solution.get("sort_order"); - if (order != null) { - config.setOrder(order.asLiteral().getInt()); - } - - sortConfigurations.put(id, config); - } - } - } finally { - model.leaveCriticalSection(); - } - return sortConfigurations; - } - - public static MapsortFilters(Map filters) { - List> list = new LinkedList<>(filters.entrySet()); - list.sort(new FilterComparator()); - return list.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new)); - } - - public static class FilterComparator implements Comparator>{ - public int compare(Entry obj1, Entry obj2) { - SearchFilter filter1 = obj1.getValue(); - SearchFilter filter2 = obj2.getValue(); - int result = filter1.getOrder().compareTo(filter2.getOrder()); - if (result == 0) { - // order are equal, sort by name - return filter1.getName().toLowerCase().compareTo(filter2.getName().toLowerCase()); - } - else { - return result; - } - } - } - - private static SearchFilter createSearchFilter(VitroRequest vreq, Map filtersByField, - QuerySolution solution, String resultFilterId, String resultFieldName) { - SearchFilter filter; - filter = new SearchFilter(resultFilterId); - filtersByField.put(resultFieldName, filter); - filter.setName(solution.get("filter_label")); - filter.setOrder(solution.get("filter_order")); - filter.setType(solution.get("filter_type")); - if (solution.get("isUri") != null && "true".equals(solution.get("isUri").toString())) { - filter.setLocalizationRequired(true); - } - RDFNode min = solution.get("min"); - if (min != null) { - filter.setMin(min.asLiteral().toString()); - } - RDFNode max = solution.get("max"); - if (max != null) { - filter.setMax(max.asLiteral().toString()); - } - filter.setField(resultFieldName); - RDFNode multivalued = solution.get("multivalued"); - if (multivalued != null) { - filter.setMultivalued(multivalued.asLiteral().getBoolean()); - } - RDFNode input = solution.get("input"); - if (input != null) { - filter.setInput(input.asLiteral().getBoolean()); - } - RDFNode inputRegex = solution.get("regex"); - if (inputRegex != null) { - filter.setInputRegex(inputRegex.asLiteral().getBoolean()); - } - - RDFNode publicNode = solution.get("public"); - if (publicNode != null) { - filter.setPublic(publicNode.asLiteral().getBoolean()); - } - - RDFNode facet = solution.get("facet"); - if (facet != null) { - filter.setFacetsRequired(facet.asLiteral().getBoolean()); - } - - RDFNode moreLimit = solution.get("more_limit"); - if (moreLimit != null&& moreLimit.isLiteral()) { - filter.setMoreLimit(moreLimit.asLiteral().getInt()); - } - - filter.setInputText(getFilterInputText(vreq, resultFilterId)); - filter.setRangeValues(getFilterRangeText(vreq, resultFilterId)); - - return filter; - } - - - - static boolean isEmptyValues(List requestedValues) { - if (requestedValues.isEmpty()) { - return true; - } - for (String value : requestedValues) { - if (!StringUtils.isBlank(value)) { - return false; - } - } - return true; - } - - static String getUriLabel(String uri, VitroRequest vreq) { - String result = ""; - Model model = ModelAccess.on(vreq).getOntModelSelector().getFullModel(); - model.enterCriticalSection(Lock.READ); - try { - QuerySolutionMap initialBindings = new QuerySolutionMap(); - initialBindings.add("uri", ResourceFactory.createResource(uri)); - Query facetQuery = QueryFactory.create(SearchFiltering.LABEL_QUERY); - QueryExecution qexec = QueryExecutionFactory.create(facetQuery, model, initialBindings); - ResultSet results = qexec.execSelect(); - if (results.hasNext()) { - QuerySolution solution = results.nextSolution(); - RDFNode rdfNode = solution.get("label"); - Literal literal = rdfNode.asLiteral(); - result = literal.getLexicalForm(); - } else { - result = uri; - } - }catch(Exception e) { - log.error(e, e); - }finally { - model.leaveCriticalSection(); - } - return result; - } - - static void addFacetFieldsToQuery(Map filters, SearchQuery query) { - for (String fieldId : filters.keySet()) { - SearchFilter filter = filters.get(fieldId); - if (filter.isFacetsRequired()) { - query.addFacetFields(fieldId); - } - } - } - - static Map getFiltersById(Map filtersByField) { - Map filtersById = filtersByField.values().stream() - .collect(Collectors.toMap(SearchFilter::getId, Function.identity())); - return filtersById; - } - - static Map getFiltersForTemplate(Map filtersByField) { - Iterator> iterator = filtersByField.entrySet().iterator(); - while (iterator.hasNext()) { - Entry entry = iterator.next(); - SearchFilter searchFilter = entry.getValue(); - searchFilter.removeValuesWithZeroCount(); - if (searchFilter.isEmpty()) { - searchFilter.setHidden(true); - } - - } - return filtersByField.values().stream().collect(Collectors.toMap(SearchFilter::getId, Function.identity())); - } - - static void addFiltersToPageLinks(VitroRequest vreq, ParamMap pagingLinkParams, Enumeration paramNames) { - while (paramNames.hasMoreElements()) { - String paramFilterName = paramNames.nextElement(); - if (!StringUtils.isBlank(paramFilterName) && (paramFilterName.startsWith(FILTERS) - || paramFilterName.startsWith(FILTER_RANGE) - || paramFilterName.startsWith(FILTER_INPUT_PREFIX) - || paramFilterName.startsWith(ExtendedSearchController.PARAM_QUERY_SORT_BY))) { - String[] values = vreq.getParameterValues(paramFilterName); - if (values.length > 0) { - pagingLinkParams.put(paramFilterName, values[0]); - } - } - } - } - - private static long getSpentTime(long startTime) { - return (System.nanoTime() - startTime )/1000000; - } + private static final Log log = LogFactory.getLog(SearchFiltering.class); + + private static final String FILTER_RANGE = "filter_range_"; + private static final String FILTER_INPUT_PREFIX = "filter_input_"; + private static final String FILTERS = "filters"; + + private static final String FILTER_QUERY = "" + + "PREFIX vitro: \n" + + "PREFIX vitro: \n" + + "PREFIX search: \n" + + "PREFIX gesah: \n" + + "PREFIX rdf: \n" + + "PREFIX rdfs: \n" + + "SELECT ?filter_id ?filter_type ?filter_label ?value_label ?value_id ?field_name ?public ?filter_order " + + "?value_order (STR(?isUriReq) as ?isUri ) ?multivalued ?input ?regex ?facet ?min ?max ?public_default " + + "?value_public ?more_limit \n" + + "WHERE {\n" + + " ?filter rdf:type search:Filter .\n" + + " ?filter rdfs:label ?filter_label .\n" + + " ?filter search:id ?filter_id .\n" + + " ?filter ?filter_type .\n" + + " ?filter search:filterField ?field .\n" + + " ?field search:indexField ?field_name .\n" + + " OPTIONAL {?filter search:hasKnownValue ?value . \n" + + " ?value rdfs:label ?value_label .\n" + + " ?value search:id ?value_id .\n" + + " OPTIONAL {" + + " ?value search:order ?v_order .\n" + + " bind(?v_order as ?value_order_found).\n" + + " }\n" + + " OPTIONAL {\n" + + " ?value search:defaultPublic ?public_default .\n" + + " }\n" + + " OPTIONAL {\n" + + " ?value search:public ?value_public .\n" + + " }\n" + + " }\n" + + " OPTIONAL {?field search:multivalued ?multivalued}\n" + + " OPTIONAL {?filter search:isUriValues ?isUriReq }\n" + + " OPTIONAL {?filter search:userInput ?input }\n" + + " OPTIONAL {?filter search:userInputRegex ?regex }\n" + + " OPTIONAL {?filter search:facetResults ?facet }\n" + + " OPTIONAL {?filter search:from ?min }\n" + + " OPTIONAL {?filter search:public ?public }\n" + + " OPTIONAL {?filter search:to ?max }\n" + + " OPTIONAL {?filter search:moreLimit ?more_limit }\n" + + " OPTIONAL {\n" + + " ?filter search:order ?f_order \n" + + " bind(?f_order as ?filter_order_found).\n" + + " }\n" + + " BIND(coalesce(?filter_order_found, 0) as ?filter_order)\n" + + " BIND(coalesce(?value_order_found, 0) as ?value_order)\n" + + "} ORDER BY ?filter_id ?filter_order ?value_order"; + + private static final String FILTER_GROUPS_QUERY = "" + + "PREFIX rdf: \n" + + "PREFIX search: \n" + + "PREFIX gesah: \n" + + "PREFIX rdfs: \n" + + "SELECT ?group_id (STR(?group_l) AS ?group_label) ?filter_id ?order ?filter_order ?public\n" + + "WHERE {\n" + + " ?filter_group rdf:type search:FilterGroup .\n" + + " ?filter_group search:contains ?filter .\n" + + " ?filter_group rdfs:label ?group_l .\n" + + " ?filter_group search:id ?group_id .\n" + + " OPTIONAL { ?filter_group search:order ?order . } \n" + + " ?filter search:id ?filter_id .\n" + + " OPTIONAL {?filter_group search:public ?public }\n" + + " OPTIONAL{ ?filter search:order ?f_order .\n" + + " bind(?f_order as ?filter_order_found).\n" + + " }\n" + + " BIND(coalesce(?filter_order_found, 0) as ?filter_order)\n" + + "} ORDER BY ?order ?group_label ?filter_order"; + + private static final String LABEL_QUERY = "" + + "PREFIX rdfs: \n" + + "SELECT ?label\n" + + "WHERE {\n" + + " ?uri rdfs:label ?label .\n" + + "} LIMIT 1"; + + private static final String SORT_QUERY = "" + + "PREFIX rdf: \n" + + "PREFIX rdfs: \n" + + "PREFIX gesah: \n" + + "Prefix search: \n" + + "SELECT ( STR(?sort_label) as ?label ) ?id ?searchField ?multilingual ?isAsc ?sort_order \n" + + "WHERE {\n" + + " ?sort rdf:type search:Sort . \n" + + " ?sort rdfs:label ?sort_label .\n" + + " ?sort search:sortField ?field .\n" + + " ?sort search:id ?id .\n" + + " ?field search:indexField ?searchField .\n" + + " OPTIONAL {\n" + + " ?field search:isLanguageSpecific ?f_multilingual .\n" + + " BIND(?f_multilingual as ?bind_multilingual) .\n" + + " }\n" + + " OPTIONAL {\n" + + " ?sort search:isAscending ?f_ord .\n" + + " BIND(?f_ord as ?f_order) .\n" + + " }\n" + + " OPTIONAL{ " + + " ?sort search:order ?s_order .\n" + + " BIND(?s_order as ?sort_order_found).\n" + + " }\n" + + " BIND(coalesce(?sort_order_found, 0) as ?sort_order)\n" + + " BIND(COALESCE(?f_order, false) as ?isAsc)\n" + + " BIND(COALESCE(?bind_multilingual, false) as ?multilingual)\n" + + "} ORDER BY ?sort_order ?label "; + + public static void addFiltersToQuery(VitroRequest vreq, SearchQuery query, Map filterById) { + Enumeration paramNames = vreq.getParameterNames(); + while (paramNames.hasMoreElements()) { + String paramFilterName = paramNames.nextElement(); + if (!StringUtils.isBlank(paramFilterName) && paramFilterName.startsWith(SearchFiltering.FILTERS)) { + String[] filters = vreq.getParameterValues(paramFilterName); + if (filters != null && filters.length > 0) { + for (String filter : filters) { + String[] pair = filter.split(":", 2); + if (pair.length == 2) { + String name = pair[0].replace("\"", ""); + String value = pair[1].replace("\"", ""); + SearchFilter searchFilter = filterById.get(name); + if (searchFilter != null && searchFilter.getField() != null) { + query.addFilterQuery(searchFilter.getField() + ":\"" + value + "\""); + } + + } + } + } + + } + } + for (String filterId : filterById.keySet()) { + SearchFilter searchFilter = filterById.get(filterId); + if (searchFilter.isInput()) { + SearchFiltering.addInputFilter(query, searchFilter); + } else if (searchFilter.isRange()) { + SearchFiltering.addRangeFilter(query, searchFilter); + } + for (FilterValue fv : searchFilter.getValues().values()) { + if (fv.isDefaultPublic()) { + query.addFilterQuery(searchFilter.getField() + ":\"" + fv.getId() + "\""); + } + } + } + } + + public static Map> getRequestFilters(VitroRequest vreq) { + Map> requestFilters = new HashMap<>(); + Enumeration paramNames = vreq.getParameterNames(); + while (paramNames.hasMoreElements()) { + String paramFilterName = paramNames.nextElement(); + if (!StringUtils.isBlank(paramFilterName) && paramFilterName.startsWith(SearchFiltering.FILTERS)) { + String[] filters = vreq.getParameterValues(paramFilterName); + if (filters != null && filters.length > 0) { + for (String filter : filters) { + String[] pair = filter.split(":", 2); + if (pair.length == 2) { + String name = pair[0].replace("\"", ""); + String value = pair[1].replace("\"", ""); + if (requestFilters.containsKey(name)) { + List list = requestFilters.get(name); + list.add(value); + } else { + requestFilters.put(name, new LinkedList(Arrays.asList(value))); + } + } + } + } + } + } + + return requestFilters; + } + + public static Map readFilterConfigurations(VitroRequest vreq) { + long startTime = System.nanoTime(); + + Map filtersByField = new LinkedHashMap<>(); + Model model = ModelAccess.on(vreq).getOntModelSelector().getABoxModel(); + model.enterCriticalSection(Lock.READ); + try { + Query facetQuery = QueryFactory.create(FILTER_QUERY); + QueryExecution qexec = QueryExecutionFactory.create(facetQuery, model); + ResultSet results = qexec.execSelect(); + while (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + if (solution.get("filter_id") == null || solution.get("field_name") == null + || solution.get("filter_type") == null) { + continue; + } + String resultFilterId = solution.get("filter_id").toString(); + String resultFieldName = solution.get("field_name").toString(); + + SearchFilter filter = null; + if (filtersByField.containsKey(resultFieldName)) { + filter = filtersByField.get(resultFieldName); + } else { + filter = createSearchFilter(vreq, filtersByField, solution, resultFilterId, resultFieldName); + } + if (solution.get("value_id") == null) { + continue; + } + String valueId = solution.get("value_id").toString(); + if (!filter.contains(valueId)) { + FilterValue value = new FilterValue(valueId); + value.setName(solution.get("value_label")); + value.setOrder(solution.get("value_order")); + value.setPubliclyAvailable(solution.get("value_public")); + filter.addValue(value); + RDFNode pubDefault = solution.get("public_default"); + if (pubDefault != null && pubDefault.asLiteral().getBoolean() && isNotLoggedIn(vreq)) { + value.setDefaultPublic(true); + } + } + } + } finally { + model.leaveCriticalSection(); + } + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after FILTER QUERY request."); + } + Map> requestFilters = getRequestFilters(vreq); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after getRequestFilters."); + } + setSelectedFilters(filtersByField, requestFilters); + if (log.isDebugEnabled()) { + log.debug(getSpentTime(startTime) + "ms spent after setSelectedFilters."); + } + return sortFilters(filtersByField); + } + + public static Map sortFilters(Map filters) { + List> list = new LinkedList<>(filters.entrySet()); + list.sort(new FilterComparator()); + return list.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> b, LinkedHashMap::new)); + } + + public static class FilterComparator implements Comparator> { + public int compare(Entry obj1, Entry obj2) { + SearchFilter filter1 = obj1.getValue(); + SearchFilter filter2 = obj2.getValue(); + int result = filter1.getOrder().compareTo(filter2.getOrder()); + if (result == 0) { + // order are equal, sort by name + return filter1.getName().toLowerCase().compareTo(filter2.getName().toLowerCase()); + } else { + return result; + } + } + } + + public static List readFilterGroupsConfigurations(VitroRequest vreq, + Map filtersById) { + Map groups = new LinkedHashMap<>(); + Model model = ModelAccess.on(vreq).getOntModelSelector().getABoxModel(); + model.enterCriticalSection(Lock.READ); + try { + Query facetQuery = QueryFactory.create(FILTER_GROUPS_QUERY); + QueryExecution qexec = QueryExecutionFactory.create(facetQuery, model); + ResultSet results = qexec.execSelect(); + while (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + if (solution.get("filter_id") == null || solution.get("group_label") == null + || solution.get("group_id") == null) { + continue; + } + String filterId = solution.get("filter_id").toString(); + String groupId = solution.get("group_id").toString(); + String groupLabel = solution.get("group_label").toString(); + SearchFilterGroup group = null; + if (groups.containsKey(groupId)) { + group = groups.get(groupId); + } else { + group = new SearchFilterGroup(groupId, groupLabel); + RDFNode publicNode = solution.get("public"); + if (publicNode != null) { + group.setPublic(publicNode.asLiteral().getBoolean()); + } + groups.put(groupId, group); + } + group.addFilterId(filterId); + SearchFilter filter = filtersById.get(filterId); + if (filter != null && !filter.isHidden()) { + group.setHidden(false); + } + } + } finally { + model.leaveCriticalSection(); + } + return new LinkedList(groups.values()); + } + + public static Map getSortConfigurations(VitroRequest vreq) { + Map sortConfigurations = new LinkedHashMap<>(); + Model model = ModelAccess.on(vreq).getOntModelSelector().getABoxModel(); + model.enterCriticalSection(Lock.READ); + try { + Query facetQuery = QueryFactory.create(SORT_QUERY); + QueryExecution qexec = QueryExecutionFactory.create(facetQuery, model); + ResultSet results = qexec.execSelect(); + while (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + if (solution.get("label") == null || solution.get("id") == null + || solution.get("searchField") == null) { + continue; + } + String field = solution.get("searchField").toString(); + String id = solution.get("id").toString(); + String label = solution.get("label").toString(); + + SortConfiguration config = null; + if (sortConfigurations.containsKey(id)) { + config = sortConfigurations.get(id); + } else { + config = new SortConfiguration(id, label, field); + + RDFNode multilingual = solution.get("multilingual"); + if (multilingual != null) { + config.setMultilingual(multilingual.asLiteral().getBoolean()); + } + RDFNode isAsc = solution.get("isAsc"); + if (isAsc != null) { + config.setAscOrder(isAsc.asLiteral().getBoolean()); + } + + RDFNode order = solution.get("sort_order"); + if (order != null) { + config.setOrder(order.asLiteral().getInt()); + } + sortConfigurations.put(id, config); + } + } + } finally { + model.leaveCriticalSection(); + } + return sortConfigurations; + } + + private static SearchFilter createSearchFilter(VitroRequest vreq, Map filtersByField, + QuerySolution solution, String resultFilterId, String resultFieldName) { + SearchFilter filter; + filter = new SearchFilter(resultFilterId); + filtersByField.put(resultFieldName, filter); + filter.setName(solution.get("filter_label")); + filter.setOrder(solution.get("filter_order")); + filter.setType(solution.get("filter_type")); + if (solution.get("isUri") != null && "true".equals(solution.get("isUri").toString())) { + filter.setLocalizationRequired(true); + } + RDFNode min = solution.get("min"); + if (min != null) { + filter.setMin(min.asLiteral().toString()); + } + RDFNode max = solution.get("max"); + if (max != null) { + filter.setMax(max.asLiteral().toString()); + } + filter.setField(resultFieldName); + RDFNode multivalued = solution.get("multivalued"); + if (multivalued != null) { + filter.setMultivalued(multivalued.asLiteral().getBoolean()); + } + RDFNode input = solution.get("input"); + if (input != null) { + filter.setInput(input.asLiteral().getBoolean()); + } + RDFNode inputRegex = solution.get("regex"); + if (inputRegex != null) { + filter.setInputRegex(inputRegex.asLiteral().getBoolean()); + } + + RDFNode publicNode = solution.get("public"); + if (publicNode != null) { + filter.setPublic(publicNode.asLiteral().getBoolean()); + } + + RDFNode facet = solution.get("facet"); + if (facet != null) { + filter.setFacetsRequired(facet.asLiteral().getBoolean()); + } + + RDFNode moreLimit = solution.get("more_limit"); + if (moreLimit != null && moreLimit.isLiteral()) { + filter.setMoreLimit(moreLimit.asLiteral().getInt()); + } + + filter.setInputText(getFilterInputText(vreq, resultFilterId)); + filter.setRangeValues(getFilterRangeText(vreq, resultFilterId)); + + return filter; + } + + private static void addRangeFilter(SearchQuery query, SearchFilter searchFilter) { + String rangeText = searchFilter.getRangeText(); + if (StringUtils.isBlank(rangeText)) { + return; + } + query.addFilterQuery(searchFilter.getField() + ":\"" + rangeText + "\""); + } + + private static void addInputFilter(SearchQuery query, SearchFilter searchFilter) { + if (StringUtils.isBlank(searchFilter.getInputText()) + || ExtendedSearchController.PARAM_QUERY_TEXT.equals(searchFilter.getId())) { + return; + } + String searchText = searchFilter.getInputText(); + if (searchFilter.isInputRegex()) { + searchText = searchText.replaceAll("([ )(:])", "\\\\$1") + "*"; + query.addFilterQuery(searchFilter.getField() + ":" + searchText); + } else { + query.addFilterQuery(searchFilter.getField() + ":\"" + searchText + "\""); + } + } + + private static String getFilterInputText(VitroRequest vreq, String name) { + if (ExtendedSearchController.PARAM_QUERY_TEXT.equals(name)) { + return ExtendedSearchController.getQueryText(vreq); + } + String[] values = vreq.getParameterValues(SearchFiltering.FILTER_INPUT_PREFIX + name); + if (values != null && values.length > 0) { + return values[0]; + } + return ""; + } + + private static String getFilterRangeText(VitroRequest vreq, String name) { + String[] values = vreq.getParameterValues(SearchFiltering.FILTER_RANGE + name); + if (values != null && values.length > 0) { + return values[0]; + } + return ""; + } + + private static void setSelectedFilters(Map filtersByField, Map> requestFilters) { + for (SearchFilter filter : filtersByField.values()) { + if (requestFilters.containsKey(filter.getId())) { + List requestValues = requestFilters.get(filter.getId()); + if (!SearchFiltering.isEmptyValues(requestValues)) { + filter.setSelected(true); + for (String requestValue : requestValues) { + if (filter.getValues().containsKey(requestValue)) { + FilterValue value = filter.getValue(requestValue); + value.setSelected(true); + } + } + } + } + } + } + + private static boolean isNotLoggedIn(VitroRequest vreq) { + UserAccount user = LoginStatusBean.getCurrentUser(vreq); + return user == null; + } + + static boolean isEmptyValues(List requestedValues) { + if (requestedValues.isEmpty()) { + return true; + } + for (String value : requestedValues) { + if (!StringUtils.isBlank(value)) { + return false; + } + } + return true; + } + + static String getUriLabel(String uri, VitroRequest vreq) { + String result = ""; + Model model = ModelAccess.on(vreq).getOntModelSelector().getFullModel(); + model.enterCriticalSection(Lock.READ); + try { + QuerySolutionMap initialBindings = new QuerySolutionMap(); + initialBindings.add("uri", ResourceFactory.createResource(uri)); + Query facetQuery = QueryFactory.create(SearchFiltering.LABEL_QUERY); + QueryExecution qexec = QueryExecutionFactory.create(facetQuery, model, initialBindings); + ResultSet results = qexec.execSelect(); + if (results.hasNext()) { + QuerySolution solution = results.nextSolution(); + RDFNode rdfNode = solution.get("label"); + Literal literal = rdfNode.asLiteral(); + result = literal.getLexicalForm(); + } else { + result = uri; + } + } catch (Exception e) { + log.error(e, e); + } finally { + model.leaveCriticalSection(); + } + return result; + } + + static void addFacetFieldsToQuery(Map filters, SearchQuery query) { + for (String fieldId : filters.keySet()) { + SearchFilter filter = filters.get(fieldId); + if (filter.isFacetsRequired()) { + query.addFacetFields(fieldId); + } + } + } + + static Map getFiltersById(Map filtersByField) { + Map filtersById = + filtersByField.values().stream().collect(Collectors.toMap(SearchFilter::getId, Function.identity())); + return filtersById; + } + + static Map getFiltersForTemplate(Map filtersByField) { + Iterator> iterator = filtersByField.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + SearchFilter searchFilter = entry.getValue(); + searchFilter.removeValuesWithZeroCount(); + if (searchFilter.isEmpty()) { + searchFilter.setHidden(true); + } + + } + return filtersByField.values().stream().collect(Collectors.toMap(SearchFilter::getId, Function.identity())); + } + + static void addFiltersToPageLinks(VitroRequest vreq, ParamMap pagingLinkParams, Enumeration paramNames) { + while (paramNames.hasMoreElements()) { + String paramFilterName = paramNames.nextElement(); + if (!StringUtils.isBlank(paramFilterName) && (paramFilterName.startsWith(FILTERS) + || paramFilterName.startsWith(FILTER_RANGE) || paramFilterName.startsWith(FILTER_INPUT_PREFIX) + || paramFilterName.startsWith(ExtendedSearchController.PARAM_QUERY_SORT_BY))) { + String[] values = vreq.getParameterValues(paramFilterName); + if (values.length > 0) { + pagingLinkParams.put(paramFilterName, values[0]); + } + } + } + } + + private static long getSpentTime(long startTime) { + return (System.nanoTime() - startTime) / 1000000; + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SortConfiguration.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SortConfiguration.java index 089933b704..9a4f9294fd 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SortConfiguration.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/search/controller/SortConfiguration.java @@ -6,83 +6,85 @@ public class SortConfiguration { - private String id = ""; - private String field = ""; - private boolean multilingual = false; - private boolean ascOrder = false; - private boolean selected = false; - private String label = ""; - private int order = 0; - public SortConfiguration(String id, String label, String field) { - this.id = id; - this.setLabel(label); - this.field = field; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getField() { - return field; - } - - public void setField(String field) { - this.field = field; - } - - public boolean isMultilingual() { - return multilingual; - } - - public void setMultilingual(boolean multilingual) { - this.multilingual = multilingual; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public String getField(Locale locale) { - String languageTag = locale.toLanguageTag(); - if (multilingual) { - return languageTag + field; - } - return field; - } - - public Order getSortOrder() { - if (ascOrder) { - return Order.ASC; - } - return Order.DESC; - } - public void setAscOrder(boolean ascOrder) { - this.ascOrder = ascOrder; - } - - public boolean isSelected() { - return selected; - } - - public void setSelected(boolean selected) { - this.selected = selected; - } - - public int getOrder() { - return order; - } - - public void setOrder(int order) { - this.order = order; - } + private String id = ""; + private String field = ""; + private boolean multilingual = false; + private boolean ascOrder = false; + private boolean selected = false; + private String label = ""; + private int order = 0; + + public SortConfiguration(String id, String label, String field) { + this.id = id; + this.setLabel(label); + this.field = field; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } + + public boolean isMultilingual() { + return multilingual; + } + + public void setMultilingual(boolean multilingual) { + this.multilingual = multilingual; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getField(Locale locale) { + String languageTag = locale.toLanguageTag(); + if (multilingual) { + return languageTag + field; + } + return field; + } + + public Order getSortOrder() { + if (ascOrder) { + return Order.ASC; + } + return Order.DESC; + } + + public void setAscOrder(boolean ascOrder) { + this.ascOrder = ascOrder; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } }