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}