001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.frontoffice.search.requesttime.impl; 017 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Optional; 023import java.util.stream.Collectors; 024 025import org.apache.avalon.framework.parameters.Parameters; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.cocoon.xml.AttributesImpl; 030import org.apache.cocoon.xml.XMLUtils; 031import org.xml.sax.ContentHandler; 032import org.xml.sax.SAXException; 033 034import org.ametys.cms.search.SearchResults; 035import org.ametys.cms.search.advanced.AbstractTreeNode; 036import org.ametys.cms.search.advanced.TreeLeaf; 037import org.ametys.cms.search.solr.SearcherFactory; 038import org.ametys.cms.search.solr.SearcherFactory.Searcher; 039import org.ametys.core.right.AllowedUsers; 040import org.ametys.core.right.RightManager; 041import org.ametys.plugins.repository.AmetysObject; 042import org.ametys.web.frontoffice.search.instance.SearchServiceInstance; 043import org.ametys.web.frontoffice.search.instance.model.RightCheckingMode; 044import org.ametys.web.frontoffice.search.instance.model.SearchServiceCriterion; 045import org.ametys.web.frontoffice.search.metamodel.SearchServiceCriterionDefinition; 046import org.ametys.web.frontoffice.search.metamodel.SearchServiceFacetDefinition; 047import org.ametys.web.frontoffice.search.requesttime.AbstractSearchComponent; 048import org.ametys.web.frontoffice.search.requesttime.SearchComponent; 049import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments; 050import org.ametys.web.frontoffice.search.requesttime.input.impl.FormSearchUserInputs; 051 052/** 053 * {@link SearchComponent} for saxing number of results for each values of enumerated criteria 054 */ 055public class SaxEnumeratedCriteriaComponent extends AbstractSearchComponent implements Serviceable 056{ 057 /** The searcher factory */ 058 protected SearcherFactory _searcherFactory; 059 060 /** The helper for search component */ 061 protected SearchComponentHelper _searchComponentHelper; 062 063 /** The right manager */ 064 protected RightManager _rightManager; 065 066 public void service(ServiceManager manager) throws ServiceException 067 { 068 _searcherFactory = (SearcherFactory) manager.lookup(SearcherFactory.ROLE); 069 _searchComponentHelper = (SearchComponentHelper) manager.lookup(SearchComponentHelper.ROLE); 070 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 071 } 072 073 @Override 074 public int getPriority() 075 { 076 return SEARCH_PRIORITY + 2500; 077 } 078 079 @Override 080 public boolean supports(SearchComponentArguments args) 081 { 082 return args.serviceInstance().computeCriteriaCounts(); 083 } 084 085 @Override 086 public void execute(SearchComponentArguments args) throws Exception 087 { 088 ContentHandler contentHandler = args.contentHandler(); 089 XMLUtils.startElement(contentHandler, "enumerated-criteria"); 090 091 SearchServiceInstance serviceInstance = args.serviceInstance(); 092 093 // Transform each enumerated criteria in facet definition to have the number of result for each enumerated values 094 Map<SearchServiceFacetDefinition, SearchServiceCriterion> serviceFacets = _getFacetDefinitions(serviceInstance); 095 if (!serviceFacets.isEmpty()) 096 { 097 Searcher searcher = _searcherFactory.create(); 098 099 // Set right 100 _setRight(searcher, args); 101 102 // Add criterion query 103 searcher.withQuery(_searchComponentHelper.getCriterionTreeQuery(args, false, false)); 104 105 searcher.withFacets(serviceFacets.keySet().stream() 106 .map(SearchServiceFacetDefinition::getFacetDefinition) 107 .collect(Collectors.toList())); 108 109 // Add filter query 110 searcher.addFilterQuery(_searchComponentHelper.getFilterQuery(args)); 111 112 // Launch search with facets 113 SearchResults<AmetysObject> results = searcher.searchWithFacets(); 114 args.setEnumeratedResults(results); 115 116 // Sax the number of result for each values of enumerated criteria 117 Map<String, Object> contextualParameters = SearchComponentHelper.getSearchComponentContextualParameters(args); 118 _saxCountEnumeratedCriteria(contentHandler, args.generatorParameters(), serviceFacets, results, contextualParameters); 119 } 120 121 XMLUtils.endElement(contentHandler, "enumerated-criteria"); 122 } 123 124 /** 125 * Transform each enumerated criteria in facet definition to have the number of result for each enumerated values 126 * @param serviceInstance the service instance 127 * @return the collection of facet definition 128 */ 129 protected Map<SearchServiceFacetDefinition, SearchServiceCriterion> _getFacetDefinitions(SearchServiceInstance serviceInstance) 130 { 131 Map<SearchServiceFacetDefinition, SearchServiceCriterion> facets = new HashMap<>(); 132 List<SearchServiceCriterion> criteria = serviceInstance.getCriterionTree() 133 .map(AbstractTreeNode::getFlatLeaves) 134 .orElseGet(Collections::emptyList) 135 .stream() 136 .map(TreeLeaf::getValue) 137 .filter(c -> !c.getMode().isStatic()) 138 .collect(Collectors.toList()); 139 140 for (SearchServiceCriterion<?> criterion : criteria) 141 { 142 serviceInstance.getReturnables() 143 .stream() 144 .map(returnable -> returnable.getFacetDefinition(criterion.getCriterionDefinition())) 145 .filter(Optional::isPresent) 146 .map(Optional::get) 147 // Criterion are linked to a searchable, not a returnable. But here we use the returnable to build a facet from it. 148 // We need a way to choose the facet when multiple definitions are available. 149 // We don't know of any real use case so we use findFirst here but it may require a more deterministic algorithm. 150 .findFirst() 151 .ifPresent(facet -> facets.put(facet, criterion)); 152 } 153 154 return facets; 155 } 156 157 /** 158 * Set right to the searcher 159 * @param searcher the searcher 160 * @param args the arguments 161 */ 162 protected void _setRight(Searcher searcher, SearchComponentArguments args) 163 { 164 RightCheckingMode rightCheckingMode = args.serviceInstance().getRightCheckingMode(); 165 switch (rightCheckingMode) 166 { 167 case EXACT: 168 searcher.setCheckRights(true); 169 break; 170 case FAST: 171 AllowedUsers allowedUsersOnPage = _rightManager.getReadAccessAllowedUsers(args.currentPage()); 172 searcher.checkRightsComparingTo(allowedUsersOnPage); 173 break; 174 case NONE: 175 searcher.setCheckRights(false); 176 break; 177 default: 178 throw new IllegalStateException("Unhandled right checking mode: " + rightCheckingMode); 179 } 180 } 181 182 /** 183 * SAX the enumerated criteria 184 * @param contentHandler the content handler 185 * @param parameters the parameters 186 * @param facets The facets 187 * @param searchResults The search results 188 * @param contextualParameters The contextual parameters 189 * @throws SAXException if an error occurs while generating SAX events 190 */ 191 protected void _saxCountEnumeratedCriteria(ContentHandler contentHandler, Parameters parameters, Map<SearchServiceFacetDefinition, SearchServiceCriterion> facets, SearchResults<AmetysObject> searchResults, Map<String, Object> contextualParameters) throws SAXException 192 { 193 Map<String, Integer> valuesForCurrentFacetDef = Collections.EMPTY_MAP; 194 Map<String, Map<String, Integer>> facetResults = searchResults.getFacetResults(); 195 196 for (SearchServiceFacetDefinition facet : facets.keySet()) 197 { 198 SearchServiceCriterion<?> criterion = facets.get(facet); 199 contextualParameters.put("criterion", criterion); 200 201 String facetName = facet.getName(); 202 String criterionName = FormSearchUserInputs.CRITERION_PREFIX + criterion.getName(); 203 AttributesImpl attrs = new AttributesImpl(); 204 attrs.addCDATAAttribute("name", criterionName); 205 if (facetResults.containsKey(facetName)) 206 { 207 valuesForCurrentFacetDef = _getFacetValues(facetResults.get(facetName), criterion, contextualParameters); 208 } 209 attrs.addCDATAAttribute("total", String.valueOf(valuesForCurrentFacetDef.values() 210 .stream() 211 .mapToInt(Integer::intValue) 212 .sum())); 213 214 XMLUtils.startElement(contentHandler, "criterion", attrs); 215 _saxFacetItemsWithCount(contentHandler, facet, valuesForCurrentFacetDef, contextualParameters); 216 XMLUtils.endElement(contentHandler, "criterion"); 217 } 218 } 219 220 /** 221 * Retrieves the values of the current facet 222 * @param facetResult the result of the current facet 223 * @param criterion the criterion corresponding to the current facet 224 * @param contextualParameters the contextual parameters 225 * @param <T> Type of the criterion value 226 * @return the values of the current facet 227 */ 228 protected <T> Map<String, Integer> _getFacetValues(Map<String, Integer> facetResult, SearchServiceCriterion<T> criterion, Map<String, Object> contextualParameters) 229 { 230 SearchServiceCriterionDefinition<T> criterionDefinition = criterion.getCriterionDefinition(); 231 Map<String, Integer> valuesForCurrentFacetDef = new HashMap<>(); 232 233 for (Map.Entry<String, Integer> facetResultEntry : facetResult.entrySet()) 234 { 235 String facetResultValue = facetResultEntry.getKey(); 236 Integer facetResultCount = facetResultEntry.getValue(); 237 238 String value = criterionDefinition.facetValueToSAX(facetResultValue, contextualParameters); 239 valuesForCurrentFacetDef.put(value, facetResultCount); 240 } 241 242 return valuesForCurrentFacetDef; 243 } 244 245 /** 246 * SAX the facet items with the count 247 * @param contentHandler the content handler 248 * @param facet the facet definition 249 * @param valuesForCurrentFacetDef The values for the current facet definition 250 * @param contextualParameters The contextual parameters 251 * @throws SAXException if an error occurs while generating SAX events 252 */ 253 protected void _saxFacetItemsWithCount(ContentHandler contentHandler, SearchServiceFacetDefinition facet, Map<String, Integer> valuesForCurrentFacetDef, Map<String, Object> contextualParameters) throws SAXException 254 { 255 for (String value : valuesForCurrentFacetDef.keySet()) 256 { 257 Integer count = valuesForCurrentFacetDef.get(value); 258 _saxFacetItemWithCount(contentHandler, value, count); 259 } 260 } 261 262 /** 263 * SAX the facet item with the count 264 * @param contentHandler the content handler 265 * @param value the value for the current facet item 266 * @param count the value count 267 * @throws SAXException if an error occurs while generating SAX events 268 */ 269 protected void _saxFacetItemWithCount(ContentHandler contentHandler, String value, Integer count) throws SAXException 270 { 271 AttributesImpl valueAttrs = new AttributesImpl(); 272 valueAttrs.addCDATAAttribute("value", value); 273 valueAttrs.addCDATAAttribute("count", String.valueOf(count)); 274 275 XMLUtils.createElement(contentHandler, "item", valueAttrs); 276 } 277}