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.runtime.i18n.I18nizableText; 043import org.ametys.web.frontoffice.search.instance.SearchServiceInstance; 044import org.ametys.web.frontoffice.search.instance.model.FOSearchCriterion; 045import org.ametys.web.frontoffice.search.instance.model.RightCheckingMode; 046import org.ametys.web.frontoffice.search.metamodel.EnumeratedValues; 047import org.ametys.web.frontoffice.search.metamodel.FacetDefinition; 048import org.ametys.web.frontoffice.search.metamodel.SearchCriterionDefinition; 049import org.ametys.web.frontoffice.search.metamodel.impl.ContentFacetDefinition; 050import org.ametys.web.frontoffice.search.metamodel.impl.ContentSearchCriterionDefinition; 051import org.ametys.web.frontoffice.search.requesttime.SearchComponent; 052import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments; 053import org.ametys.web.frontoffice.search.requesttime.input.impl.FormSearchUserInputs; 054 055/** 056 * {@link SearchComponent} for saxing number of results for each values of enumerated criteria 057 */ 058public class SaxEnumeratedCriteriaComponent implements SearchComponent, Serviceable 059{ 060 /** The searcher factory */ 061 protected SearcherFactory _searcherFactory; 062 063 /** The helper for search component */ 064 protected SearchComponentHelper _searchComponentHelper; 065 066 /** The right manager */ 067 protected RightManager _rightManager; 068 069 public void service(ServiceManager manager) throws ServiceException 070 { 071 _searcherFactory = (SearcherFactory) manager.lookup(SearcherFactory.ROLE); 072 _searchComponentHelper = (SearchComponentHelper) manager.lookup(SearchComponentHelper.ROLE); 073 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 074 } 075 076 @Override 077 public int priority() 078 { 079 return SEARCH_PRIORITY + 2500; 080 } 081 082 @Override 083 public boolean supports(SearchComponentArguments args) 084 { 085 return args.serviceInstance().computeCriteriaCounts(); 086 } 087 088 @Override 089 public void execute(SearchComponentArguments args) throws Exception 090 { 091 ContentHandler contentHandler = args.contentHandler(); 092 XMLUtils.startElement(contentHandler, "enumerated-criteria"); 093 094 SearchServiceInstance serviceInstance = args.serviceInstance(); 095 096 // Transform each enumerated criteria in facet definition to have the number of result for each enumerated values 097 Map<FacetDefinition, FOSearchCriterion> serviceFacets = _getFacetDefinitions(serviceInstance); 098 if (!serviceFacets.isEmpty()) 099 { 100 Searcher searcher = _searcherFactory.create(); 101 102 // Set right 103 _setRight(searcher, args); 104 105 // Add criterion query 106 searcher.withQuery(_searchComponentHelper.getCriterionTreeQuery(args, false, false)); 107 108 searcher.withFacets(serviceFacets.keySet().stream() 109 .map(FacetDefinition::getSearchField) 110 .collect(Collectors.toList())); 111 112 // Add filter query 113 searcher.addFilterQuery(_searchComponentHelper.getFilterQuery(args)); 114 115 // Launch search with facets 116 SearchResults<AmetysObject> results = searcher.searchWithFacets(); 117 args.setEnumeratedResults(results); 118 119 // Sax the number of result for each values of enumerated criteria 120 Map<String, Object> contextualParameters = SearchComponentHelper.getSearchComponentContextualParameters(args); 121 _saxCountEnumeratedCriteria(contentHandler, args.generatorParameters(), serviceFacets, results, contextualParameters); 122 } 123 124 XMLUtils.endElement(contentHandler, "enumerated-criteria"); 125 } 126 127 /** 128 * Transform each enumerated criteria in facet definition to have the number of result for each enumerated values 129 * @param serviceInstance the service instance 130 * @return the collection of facet definition 131 */ 132 protected Map<FacetDefinition, FOSearchCriterion> _getFacetDefinitions(SearchServiceInstance serviceInstance) 133 { 134 Map<FacetDefinition, FOSearchCriterion> facets = new HashMap<>(); 135 List<FOSearchCriterion> criteria = serviceInstance.getCriterionTree() 136 .map(AbstractTreeNode::getFlatLeaves) 137 .orElseGet(Collections::emptyList) 138 .stream() 139 .map(TreeLeaf::getValue) 140 .filter(c -> !c.getMode().isStatic()) 141 .collect(Collectors.toList()); 142 143 for (FOSearchCriterion criterion : criteria) 144 { 145 Optional.of(criterion.getCriterionDefinition()) 146 .filter(ContentSearchCriterionDefinition.class::isInstance) 147 .map(ContentSearchCriterionDefinition.class::cast) 148 .filter(critDef -> critDef.getSearchUICriterion().isFacetable()) 149 .map(critDef -> new ContentFacetDefinition(critDef, null)) 150 .ifPresent(facet -> facets.put(facet, criterion)); 151 } 152 153 return facets; 154 } 155 156 /** 157 * Set right to the searcher 158 * @param searcher the searcher 159 * @param args the arguments 160 */ 161 protected void _setRight(Searcher searcher, SearchComponentArguments args) 162 { 163 RightCheckingMode rightCheckingMode = args.serviceInstance().getRightCheckingMode(); 164 switch (rightCheckingMode) 165 { 166 case EXACT: 167 searcher.setCheckRights(true); 168 break; 169 case FAST: 170 AllowedUsers allowedUsersOnPage = _rightManager.getReadAccessAllowedUsers(args.currentPage()); 171 searcher.checkRightsComparingTo(allowedUsersOnPage); 172 break; 173 case NONE: 174 searcher.setCheckRights(false); 175 break; 176 default: 177 throw new IllegalStateException("Unhandled right checking mode: " + rightCheckingMode); 178 } 179 } 180 181 /** 182 * SAX the enumerated criteria 183 * @param contentHandler the content handler 184 * @param parameters the parameters 185 * @param facets The facets 186 * @param searchResults The search results 187 * @param contextualParameters The contextual parameters 188 * @throws SAXException if an error occurs while generating SAX events 189 */ 190 protected void _saxCountEnumeratedCriteria(ContentHandler contentHandler, Parameters parameters, Map<FacetDefinition, FOSearchCriterion> facets, SearchResults<AmetysObject> searchResults, Map<String, Object> contextualParameters) throws SAXException 191 { 192 Map<String, Integer> valuesForCurrentFacetDef = Collections.EMPTY_MAP; 193 Map<String, Map<String, Integer>> facetResults = searchResults.getFacetResults(); 194 195 for (FacetDefinition facet : facets.keySet()) 196 { 197 FOSearchCriterion criterion = facets.get(facet); 198 contextualParameters.put("criterion", criterion); 199 200 String id = facet.getId(); 201 String name = FormSearchUserInputs.FACET_PREFIX + id; 202 AttributesImpl attrs = new AttributesImpl(); 203 attrs.addCDATAAttribute("name", name); 204 if (facetResults.containsKey(id)) 205 { 206 valuesForCurrentFacetDef = _getFacetValues(facetResults.get(id), criterion, contextualParameters); 207 } 208 attrs.addCDATAAttribute("total", String.valueOf(valuesForCurrentFacetDef.values() 209 .stream() 210 .mapToInt(Integer::intValue) 211 .sum())); 212 213 XMLUtils.startElement(contentHandler, "criterion", attrs); 214 facet.getLabel().toSAX(contentHandler, "label"); 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 * @return the values of the current facet 226 */ 227 protected Map<String, Integer> _getFacetValues(Map<String, Integer> facetResult, FOSearchCriterion criterion, Map<String, Object> contextualParameters) 228 { 229 SearchCriterionDefinition criterionDefinition = criterion.getCriterionDefinition(); 230 231 if (criterionDefinition.isEnumerated()) 232 { 233 Map<String, Integer> valuesForCurrentFacetDef = new HashMap<>(); 234 Optional<EnumeratedValues> enumeratedValues = criterionDefinition.getEnumeratedValues(contextualParameters); 235 236 for (Map.Entry<String, Integer> facetResultEntry : facetResult.entrySet()) 237 { 238 String facetResultValue = facetResultEntry.getKey(); 239 Integer facetResultCount = facetResultEntry.getValue(); 240 241 enumeratedValues.flatMap(eV -> eV.getValue(facetResultValue)) 242 .map(Map.Entry::getKey) 243 .map(String::valueOf) 244 .ifPresent(value -> valuesForCurrentFacetDef.put(value, facetResultCount)); 245 } 246 247 return valuesForCurrentFacetDef; 248 } 249 250 return facetResult; 251 } 252 253 /** 254 * SAX the facet items with the count 255 * @param contentHandler the content handler 256 * @param facet the facet definition 257 * @param valuesForCurrentFacetDef The values for the current facet definition 258 * @param contextualParameters The contextual parameters 259 * @throws SAXException if an error occurs while generating SAX events 260 */ 261 protected void _saxFacetItemsWithCount(ContentHandler contentHandler, FacetDefinition facet, Map<String, Integer> valuesForCurrentFacetDef, Map<String, Object> contextualParameters) throws SAXException 262 { 263 for (String value : valuesForCurrentFacetDef.keySet()) 264 { 265 Integer count = valuesForCurrentFacetDef.get(value); 266 Optional<I18nizableText> facetLabel = facet.getFacetLabel(value, contextualParameters); 267 if (facetLabel.isPresent()) 268 { 269 _saxFacetItemWithCount(contentHandler, facetLabel.get(), value, count); 270 } 271 } 272 } 273 274 /** 275 * SAX the facet item with the count 276 * @param contentHandler the content handler 277 * @param facetLabel the facet label 278 * @param value the value for the current facet item 279 * @param count the value count 280 * @throws SAXException if an error occurs while generating SAX events 281 */ 282 protected void _saxFacetItemWithCount(ContentHandler contentHandler, I18nizableText facetLabel, String value, Integer count) throws SAXException 283 { 284 AttributesImpl valueAttrs = new AttributesImpl(); 285 valueAttrs.addCDATAAttribute("value", value); 286 valueAttrs.addCDATAAttribute("count", String.valueOf(count)); 287 288 XMLUtils.startElement(contentHandler, "item", valueAttrs); 289 facetLabel.toSAX(contentHandler); 290 XMLUtils.endElement(contentHandler, "item"); 291 } 292}