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}