001/*
002 *  Copyright 2018 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.List;
019import java.util.Optional;
020
021import org.apache.cocoon.xml.AttributesImpl;
022import org.apache.cocoon.xml.XMLUtils;
023import org.slf4j.Logger;
024import org.xml.sax.ContentHandler;
025import org.xml.sax.SAXException;
026
027import org.ametys.cms.search.SearchResult;
028import org.ametys.cms.search.SearchResults;
029import org.ametys.cms.search.SearchResultsIterable;
030import org.ametys.cms.search.SearchResultsIterator;
031import org.ametys.plugins.repository.AmetysObject;
032import org.ametys.web.frontoffice.search.instance.SearchServiceInstance;
033import org.ametys.web.frontoffice.search.metamodel.AdditionalParameterValueMap;
034import org.ametys.web.frontoffice.search.metamodel.Returnable;
035import org.ametys.web.frontoffice.search.metamodel.ReturnableSaxer;
036import org.ametys.web.frontoffice.search.requesttime.SearchComponent;
037import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments;
038
039/**
040 * {@link SearchComponent} for saxing results
041 */
042public class SaxResultsSearchComponent implements SearchComponent
043{
044    @Override
045    public int priority()
046    {
047        return SEARCH_PRIORITY + 3000;
048    }
049
050    @Override
051    public boolean supports(SearchComponentArguments args)
052    {
053        return args.launchSearch() && !args.generatorParameters().getParameterAsBoolean(DISABLE_DEFAULT_SAX_PARAMETER_NAME, false);
054    }
055
056    @Override
057    public void execute(SearchComponentArguments args) throws Exception
058    {
059        SearchResults<AmetysObject> results = args.results().orElseThrow(() -> new IllegalStateException("Results have not been set yet."));
060        
061        ContentHandler handler = args.contentHandler();
062        
063        int total = Math.toIntExact(results.getTotalCount());
064        int start = args.start();
065        int resultsPerPage = args.resultsPerPage();
066        saxPagination(total, args.resultPageIndex(), start, resultsPerPage, handler);
067        
068        AttributesImpl atts = new AttributesImpl();
069        atts.addCDATAAttribute("total", String.valueOf(total));
070        XMLUtils.startElement(handler, "hits", atts);
071        SearchServiceInstance service = args.serviceInstance();
072        saxHits(results, start, args, service.getReturnables(), service.getAdditionalParameterValues());
073        XMLUtils.endElement(handler, "hits");
074    }
075    
076    /**
077     * SAX the result hits
078     * @param results The search results
079     * @param start The start index of search
080     * @param args The arguments
081     * @param returnables The returnables
082     * @param additionalParameterValues The additional parameter values
083     * @throws SAXException If an error occurs while SAXing
084     */
085    protected void saxHits(SearchResults<AmetysObject> results, int start, SearchComponentArguments args, List<Returnable> returnables, AdditionalParameterValueMap additionalParameterValues) throws SAXException
086    {
087        SearchResultsIterable<SearchResult<AmetysObject>> resultsIterable = results.getResults();
088        SearchResultsIterator<SearchResult<AmetysObject>> resultsIterator = resultsIterable.iterator();
089        for (int number = start; resultsIterator.hasNext(); number++)
090        {
091            SearchResult<AmetysObject> result = resultsIterator.next();
092            saxHit(result, number, args, returnables, additionalParameterValues);
093        }
094    }
095    
096    /**
097     * SAX the result hit
098     * @param result The search result
099     * @param number The hit number
100     * @param args The arguments
101     * @param returnables The returnables
102     * @param additionalParameterValues The additional parameter values
103     * @throws SAXException If an error occurs while SAXing
104     */
105    protected void saxHit(SearchResult<AmetysObject> result, int number, SearchComponentArguments args, List<Returnable> returnables, AdditionalParameterValueMap additionalParameterValues) throws SAXException
106    {
107        ContentHandler handler = args.contentHandler();
108        Logger logger = args.logger();
109        
110        AmetysObject ametysObject = result.getObject();
111        Optional<ReturnableSaxer> saxer = getSaxer(ametysObject, args, returnables, additionalParameterValues);
112        
113        AttributesImpl attrs = new AttributesImpl();
114        attrs.addCDATAAttribute("number", Integer.toString(number));
115        attrs.addCDATAAttribute("className", ametysObject.getClass().getCanonicalName());
116        saxer.ifPresent(s -> attrs.addCDATAAttribute("saxer", s.getClass().getName()));
117        XMLUtils.startElement(handler, "hit", attrs);
118        XMLUtils.createElement(handler, "id", ametysObject.getId());
119        
120        if (saxer.isPresent())
121        {
122            saxer.get().sax(handler, ametysObject, logger, args);
123        }
124        
125        XMLUtils.createElement(handler, "score", Float.toString(result.getScore()));
126        XMLUtils.endElement(handler, "hit");
127    }
128    
129    /**
130     * Gets the {@link ReturnableSaxer Saxer} to use for SAXing the given hit
131     * @param hit The hit to SAX
132     * @param args The arguments
133     * @param returnables The returnables for the current search service instance
134     * @param additionalParameterValues The additional parameter values
135     * @return the {@link ReturnableSaxer Saxer} to use for SAXing the given hit
136     */
137    protected Optional<ReturnableSaxer> getSaxer(AmetysObject hit, SearchComponentArguments args, List<Returnable> returnables, AdditionalParameterValueMap additionalParameterValues)
138    {
139        Logger logger = args.logger();
140        
141        // By default, take the first returnable which can sax the hit
142        return returnables.stream()
143                .map(returnable -> returnable.getSaxer(returnables, additionalParameterValues))
144                .filter(saxer -> saxer.canSax(hit, logger, args))
145                .findFirst();
146    }
147    
148    /**
149     * SAX elements for pagination
150     * @param totalHits The total number of result
151     * @param start The start index of search
152     * @param currentPage The current page index
153     * @param resultsPerPage The max number of results per page
154     * @param contentHandler The content handler
155     * @throws SAXException SAXException If an error occurs while SAXing
156     */
157    protected void saxPagination(int totalHits, int currentPage, int start, int resultsPerPage, ContentHandler contentHandler) throws SAXException
158    {
159        int nbPages = (int) Math.ceil((double) totalHits / (double) resultsPerPage);
160        
161        AttributesImpl atts = new AttributesImpl();
162        atts.addCDATAAttribute("total", String.valueOf(nbPages)); // Number of pages
163        atts.addCDATAAttribute("current", String.valueOf(currentPage)); // Current page
164        atts.addCDATAAttribute("start", String.valueOf(start)); // Index of the first hit
165        atts.addCDATAAttribute("end", start + resultsPerPage > totalHits ? String.valueOf(totalHits) : String.valueOf(start + resultsPerPage)); // Index of the last hit
166        
167        XMLUtils.createElement(contentHandler, "pagination", atts);
168    }
169}