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