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.Collection; 019import java.util.Collections; 020import java.util.Map; 021import java.util.Optional; 022import java.util.stream.Collectors; 023 024import org.apache.avalon.framework.parameters.Parameters; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028import org.apache.cocoon.xml.AttributesImpl; 029import org.apache.cocoon.xml.XMLUtils; 030import org.xml.sax.ContentHandler; 031import org.xml.sax.SAXException; 032 033import org.ametys.cms.search.SearchResults; 034import org.ametys.cms.search.advanced.AbstractTreeNode; 035import org.ametys.cms.search.advanced.TreeLeaf; 036import org.ametys.cms.search.solr.SearcherFactory; 037import org.ametys.cms.search.solr.SearcherFactory.Searcher; 038import org.ametys.core.right.AllowedUsers; 039import org.ametys.core.right.RightManager; 040import org.ametys.core.util.LambdaUtils; 041import org.ametys.plugins.repository.AmetysObject; 042import org.ametys.web.frontoffice.search.instance.SearchServiceInstance; 043import org.ametys.web.frontoffice.search.instance.model.FOSearchCriterion; 044import org.ametys.web.frontoffice.search.instance.model.RightCheckingMode; 045import org.ametys.web.frontoffice.search.metamodel.FacetDefinition; 046import org.ametys.web.frontoffice.search.metamodel.impl.ContentFacetDefinition; 047import org.ametys.web.frontoffice.search.metamodel.impl.ContentSearchCriterionDefinition; 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 implements SearchComponent, 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 priority() 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 Collection<FacetDefinition> 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.stream() 106 .map(FacetDefinition::getSearchField) 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 _saxCountEnumeratedCriteria(contentHandler, args.generatorParameters(), serviceFacets, results, args.currentLang()); 118 } 119 120 XMLUtils.endElement(contentHandler, "enumerated-criteria"); 121 } 122 123 /** 124 * Transform each enumerated criteria in facet definition to have the number of result for each enumerated values 125 * @param serviceInstance the service instance 126 * @return the collection of facet defintion 127 */ 128 protected Collection<FacetDefinition> _getFacetDefinitions(SearchServiceInstance serviceInstance) 129 { 130 return serviceInstance.getCriterionTree() 131 .map(AbstractTreeNode::getFlatLeaves) 132 .orElseGet(Collections::emptyList) 133 .stream() 134 .map(TreeLeaf::getValue) 135 .filter(c -> !c.getMode().isStatic()) 136 .map(FOSearchCriterion::getCriterionDefinition) 137 .filter(ContentSearchCriterionDefinition.class::isInstance) 138 .map(ContentSearchCriterionDefinition.class::cast) 139 .filter(critDef -> critDef.getSearchUICriterion().isFacetable()) 140 .map(critDef -> new ContentFacetDefinition(critDef, null)) 141 .collect(Collectors.toList()); 142 } 143 144 /** 145 * Set right to the searcher 146 * @param searcher the searcher 147 * @param args the arguments 148 */ 149 protected void _setRight(Searcher searcher, SearchComponentArguments args) 150 { 151 RightCheckingMode rightCheckingMode = args.serviceInstance().getRightCheckingMode(); 152 switch (rightCheckingMode) 153 { 154 case EXACT: 155 searcher.setCheckRights(true); 156 break; 157 case FAST: 158 AllowedUsers allowedUsersOnPage = _rightManager.getReadAccessAllowedUsers(args.currentPage()); 159 searcher.checkRightsComparingTo(allowedUsersOnPage); 160 break; 161 case NONE: 162 searcher.setCheckRights(false); 163 break; 164 default: 165 throw new IllegalStateException("Unhandled right checking mode: " + rightCheckingMode); 166 } 167 } 168 169 /** 170 * SAX the enumerated criteria 171 * @param contentHandler the content handler 172 * @param parameters the parameters 173 * @param facets The facets 174 * @param searchResults The search results 175 * @param currentLang The current language 176 * @throws SAXException if an error occurs while SAXing 177 */ 178 protected void _saxCountEnumeratedCriteria(ContentHandler contentHandler, Parameters parameters, Collection<FacetDefinition> facets, SearchResults<AmetysObject> searchResults, String currentLang) throws SAXException 179 { 180 Map<String, Integer> valuesForCurrentFacetDef = Collections.EMPTY_MAP; 181 Map<String, Map<String, Integer>> facetResults = searchResults.getFacetResults(); 182 183 for (FacetDefinition facet : facets) 184 { 185 String id = facet.getId(); 186 String name = FormSearchUserInputs.FACET_PREFIX + id; 187 AttributesImpl attrs = new AttributesImpl(); 188 attrs.addCDATAAttribute("name", name); 189 if (facetResults.containsKey(id)) 190 { 191 valuesForCurrentFacetDef = facetResults.get(id); 192 } 193 attrs.addCDATAAttribute("total", String.valueOf(valuesForCurrentFacetDef.values() 194 .stream() 195 .mapToInt(Integer::intValue) 196 .sum())); 197 198 XMLUtils.startElement(contentHandler, "criterion", attrs); 199 facet.getLabel().toSAX(contentHandler, "label"); 200 _saxFacetItemWithCount(contentHandler, facet, valuesForCurrentFacetDef, currentLang); 201 XMLUtils.endElement(contentHandler, "criterion"); 202 } 203 } 204 205 /** 206 * SAX the facet items with the count 207 * @param contentHandler the content handler 208 * @param facet the facet definition 209 * @param valuesForCurrentFacetDef The values for the current facet definition 210 * @param lang The current language 211 * @throws SAXException if an error occurs while SAXing 212 */ 213 protected void _saxFacetItemWithCount(ContentHandler contentHandler, FacetDefinition facet, Map<String, Integer> valuesForCurrentFacetDef, String lang) throws SAXException 214 { 215 for (String value : valuesForCurrentFacetDef.keySet()) 216 { 217 Integer count = valuesForCurrentFacetDef.get(value); 218 AttributesImpl valueAttrs = new AttributesImpl(); 219 valueAttrs.addCDATAAttribute("value", value); 220 valueAttrs.addCDATAAttribute("count", count.toString()); 221 222 XMLUtils.startElement(contentHandler, "item", valueAttrs); 223 Optional.ofNullable(facet.getFacetLabel(value, lang)) 224 .ifPresent(LambdaUtils.wrapConsumer(i18n -> i18n.toSAX(contentHandler))); 225 XMLUtils.endElement(contentHandler, "item"); 226 } 227 } 228}