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.FOSearchCriterionMode; 045import org.ametys.web.frontoffice.search.instance.model.RightCheckingMode; 046import org.ametys.web.frontoffice.search.metamodel.FacetDefinition; 047import org.ametys.web.frontoffice.search.metamodel.impl.ContentFacetDefinition; 048import org.ametys.web.frontoffice.search.metamodel.impl.ContentSearchCriterionDefinition; 049import org.ametys.web.frontoffice.search.requesttime.SearchComponent; 050import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments; 051import org.ametys.web.frontoffice.search.requesttime.input.impl.FormSearchUserInputs; 052 053/** 054 * {@link SearchComponent} for saxing number of results for each values of enumerated criteria 055 */ 056public class SaxEnumeratedCriteriaComponent implements SearchComponent, Serviceable 057{ 058 /** The searcher factory */ 059 protected SearcherFactory _searcherFactory; 060 061 /** The helper for search component */ 062 protected SearchComponentHelper _searchComponentHelper; 063 064 /** The right manager */ 065 protected RightManager _rightManager; 066 067 public void service(ServiceManager manager) throws ServiceException 068 { 069 _searcherFactory = (SearcherFactory) manager.lookup(SearcherFactory.ROLE); 070 _searchComponentHelper = (SearchComponentHelper) manager.lookup(SearchComponentHelper.ROLE); 071 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 072 } 073 074 @Override 075 public int priority() 076 { 077 return SEARCH_PRIORITY + 2500; 078 } 079 080 @Override 081 public boolean supports(SearchComponentArguments args) 082 { 083 return args.serviceInstance().computeCriteriaCounts(); 084 } 085 086 @Override 087 public void execute(SearchComponentArguments args) throws Exception 088 { 089 ContentHandler contentHandler = args.contentHandler(); 090 XMLUtils.startElement(contentHandler, "enumerated-criteria"); 091 092 SearchServiceInstance serviceInstance = args.serviceInstance(); 093 094 Searcher searcher = _searcherFactory.create(); 095 096 // Set right 097 _setRight(searcher, args); 098 099 // Add criterion query 100 searcher.withQuery(_searchComponentHelper.getCriterionTreeQuery(args, false, false)); 101 102 // Transform each enumerated criteria in facet definition to have the number of result for each enumerated values 103 Collection<FacetDefinition> serviceFacets = serviceInstance.getCriterionTree() 104 .map(AbstractTreeNode::getFlatLeaves) 105 .orElseGet(Collections::emptyList) 106 .stream() 107 .map(TreeLeaf::getValue) 108 .filter(c -> !c.getMode().isStatic()) 109 .map(FOSearchCriterion::getCriterionDefinition) 110 .filter(ContentSearchCriterionDefinition.class::isInstance) 111 .map(ContentSearchCriterionDefinition.class::cast) 112 .filter(critDef -> critDef.getSearchUICriterion().isFacetable()) 113 .map(critDef -> new ContentFacetDefinition(critDef, null)) 114 .collect(Collectors.toList()); 115 116 searcher.withFacets(serviceFacets.stream() 117 .map(FacetDefinition::getSearchField) 118 .collect(Collectors.toList())); 119 120 // Add filter query 121 searcher.addFilterQuery(_searchComponentHelper.getFilterQuery(args)); 122 123 // Launch search with facets 124 SearchResults<AmetysObject> results = searcher.searchWithFacets(); 125 126 // Sax the number of result for each values of enumerated criteria 127 _saxCountEnumeratedCriteria(contentHandler, args.generatorParameters(), serviceFacets, results, args.currentLang()); 128 129 XMLUtils.endElement(contentHandler, "enumerated-criteria"); 130 } 131 132 /** 133 * Set right to the searcher 134 * @param searcher the searcher 135 * @param args the arguments 136 */ 137 protected void _setRight(Searcher searcher, SearchComponentArguments args) 138 { 139 RightCheckingMode rightCheckingMode = args.serviceInstance().getRightCheckingMode(); 140 switch (rightCheckingMode) 141 { 142 case EXACT: 143 searcher.setCheckRights(true); 144 break; 145 case FAST: 146 AllowedUsers allowedUsersOnPage = _rightManager.getReadAccessAllowedUsers(args.currentPage()); 147 searcher.checkRightsComparingTo(allowedUsersOnPage); 148 break; 149 case NONE: 150 searcher.setCheckRights(false); 151 break; 152 default: 153 throw new IllegalStateException("Unhandled right checking mode: " + rightCheckingMode); 154 } 155 } 156 157 /** 158 * SAX the enumerated criteria 159 * @param contentHandler the content handler 160 * @param parameters the parameters 161 * @param facets The facets 162 * @param searchResults The search results 163 * @param currentLang The current language 164 * @throws SAXException if an error occurs while SAXing 165 */ 166 protected void _saxCountEnumeratedCriteria(ContentHandler contentHandler, Parameters parameters, Collection<FacetDefinition> facets, SearchResults<AmetysObject> searchResults, String currentLang) throws SAXException 167 { 168 Map<String, Integer> valuesForCurrentFacetDef = Collections.EMPTY_MAP; 169 Map<String, Map<String, Integer>> facetResults = searchResults.getFacetResults(); 170 171 for (FacetDefinition facet : facets) 172 { 173 String id = facet.getId(); 174 String name = FormSearchUserInputs.FACET_PREFIX + id; 175 AttributesImpl attrs = new AttributesImpl(); 176 attrs.addCDATAAttribute("name", name); 177 if (facetResults.containsKey(id)) 178 { 179 valuesForCurrentFacetDef = facetResults.get(id); 180 } 181 attrs.addCDATAAttribute("total", String.valueOf(valuesForCurrentFacetDef.values() 182 .stream() 183 .mapToInt(Integer::intValue) 184 .sum())); 185 186 XMLUtils.startElement(contentHandler, "criterion", attrs); 187 facet.getLabel().toSAX(contentHandler, "label"); 188 for (String value : valuesForCurrentFacetDef.keySet()) 189 { 190 Integer count = valuesForCurrentFacetDef.get(value); 191 AttributesImpl valueAttrs = new AttributesImpl(); 192 valueAttrs.addCDATAAttribute("value", value); 193 valueAttrs.addCDATAAttribute("count", count.toString()); 194 195 XMLUtils.startElement(contentHandler, "item", valueAttrs); 196 Optional.ofNullable(facet.getFacetLabel(value, currentLang)) 197 .ifPresent(LambdaUtils.wrapConsumer(i18n -> i18n.toSAX(contentHandler))); 198 XMLUtils.endElement(contentHandler, "item"); 199 } 200 XMLUtils.endElement(contentHandler, "criterion"); 201 } 202 } 203}