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; 038import org.ametys.web.frontoffice.search.requesttime.pagination.Pagination; 039 040/** 041 * {@link SearchComponent} for saxing results 042 */ 043public class SaxResultsSearchComponent implements SearchComponent 044{ 045 @Override 046 public int priority() 047 { 048 return SEARCH_PRIORITY + 3000; 049 } 050 051 @Override 052 public boolean supports(SearchComponentArguments args) 053 { 054 return args.launchSearch() && !args.generatorParameters().getParameterAsBoolean(DISABLE_DEFAULT_SAX_PARAMETER_NAME, false); 055 } 056 057 @Override 058 public void execute(SearchComponentArguments args) throws Exception 059 { 060 SearchResults<AmetysObject> results = getResults(args); 061 062 ContentHandler handler = args.contentHandler(); 063 064 int total = total(results, args.serviceInstance()); 065 066 Pagination pagination = args.pagination(); 067 saxPagination(total, pagination, handler); 068 069 AttributesImpl atts = new AttributesImpl(); 070 atts.addCDATAAttribute("total", String.valueOf(total)); 071 XMLUtils.startElement(handler, "hits", atts); 072 SearchServiceInstance service = args.serviceInstance(); 073 saxHits(results, pagination.currentStartDocIndex(), args, service.getReturnables(), service.getAdditionalParameterValues()); 074 XMLUtils.endElement(handler, "hits"); 075 } 076 077 /** 078 * Gets the results 079 * @param args The arguments 080 * @return the results 081 */ 082 protected SearchResults<AmetysObject> getResults(SearchComponentArguments args) 083 { 084 return args.results() 085 .orElseThrow(() -> new IllegalStateException("Results have not been set yet. You can refer to previous logs to find the cause. This is likely due to the Solr server which could not be reached.")); 086 } 087 088 /** 089 * The total of results 090 * @param results The results 091 * @param serviceInstance The search service instance 092 * @return The total of results 093 */ 094 protected int total(SearchResults<AmetysObject> results, SearchServiceInstance serviceInstance) 095 { 096 int solrTotalCount = Math.toIntExact(results.getTotalCount()); 097 int total = serviceInstance 098 .maxResults() 099 .map(maxResults -> Math.min(solrTotalCount, maxResults)) // if a maxResults is defined, it should not exceed it 100 .orElse(solrTotalCount); 101 return total; 102 } 103 104 /** 105 * SAX the result hits 106 * @param results The search results 107 * @param start The start index of search 108 * @param args The arguments 109 * @param returnables The returnables 110 * @param additionalParameterValues The additional parameter values 111 * @throws SAXException If an error occurs while SAXing 112 */ 113 protected void saxHits(SearchResults<AmetysObject> results, int start, SearchComponentArguments args, List<Returnable> returnables, AdditionalParameterValueMap additionalParameterValues) throws SAXException 114 { 115 SearchResultsIterable<SearchResult<AmetysObject>> resultsIterable = results.getResults(); 116 SearchResultsIterator<SearchResult<AmetysObject>> resultsIterator = resultsIterable.iterator(); 117 for (int number = start; resultsIterator.hasNext(); number++) 118 { 119 SearchResult<AmetysObject> result = resultsIterator.next(); 120 saxHit(result, number, args, returnables, additionalParameterValues); 121 } 122 } 123 124 /** 125 * SAX the result hit 126 * @param result The search result 127 * @param number The hit number 128 * @param args The arguments 129 * @param returnables The returnables 130 * @param additionalParameterValues The additional parameter values 131 * @throws SAXException If an error occurs while SAXing 132 */ 133 protected void saxHit(SearchResult<AmetysObject> result, int number, SearchComponentArguments args, List<Returnable> returnables, AdditionalParameterValueMap additionalParameterValues) throws SAXException 134 { 135 ContentHandler handler = args.contentHandler(); 136 Logger logger = args.logger(); 137 138 AmetysObject ametysObject = result.getObject(); 139 Optional<ReturnableSaxer> saxer = getSaxer(ametysObject, args, returnables, additionalParameterValues); 140 141 AttributesImpl attrs = new AttributesImpl(); 142 attrs.addCDATAAttribute("number", Integer.toString(number)); 143 attrs.addCDATAAttribute("className", ametysObject.getClass().getCanonicalName()); 144 saxer.ifPresent(s -> attrs.addCDATAAttribute("saxer", s.getIdentifier())); 145 XMLUtils.startElement(handler, "hit", attrs); 146 XMLUtils.createElement(handler, "id", ametysObject.getId()); 147 148 if (saxer.isPresent()) 149 { 150 saxer.get().sax(handler, ametysObject, logger, args); 151 } 152 153 XMLUtils.createElement(handler, "score", Float.toString(result.getScore())); 154 XMLUtils.endElement(handler, "hit"); 155 } 156 157 /** 158 * Gets the {@link ReturnableSaxer Saxer} to use for SAXing the given hit 159 * @param hit The hit to SAX 160 * @param args The arguments 161 * @param returnables The returnables for the current search service instance 162 * @param additionalParameterValues The additional parameter values 163 * @return the {@link ReturnableSaxer Saxer} to use for SAXing the given hit 164 */ 165 protected Optional<ReturnableSaxer> getSaxer(AmetysObject hit, SearchComponentArguments args, List<Returnable> returnables, AdditionalParameterValueMap additionalParameterValues) 166 { 167 Logger logger = args.logger(); 168 169 // By default, take the first returnable which can sax the hit 170 return returnables.stream() 171 .map(returnable -> returnable.getSaxer(returnables, additionalParameterValues)) 172 .filter(saxer -> saxer.canSax(hit, logger, args)) 173 .findFirst(); 174 } 175 176 /** 177 * SAX elements for pagination 178 * @param totalHits The total number of result 179 * @param pagination The pagination object 180 * @param contentHandler The content handler 181 * @throws SAXException SAXException If an error occurs while SAXing 182 */ 183 protected void saxPagination(int totalHits, Pagination pagination, ContentHandler contentHandler) throws SAXException 184 { 185 int currentPage = pagination.currentPageIndex(); 186 int start = pagination.currentStartDocIndex(); 187 int nbPages = pagination.numberOfPages(totalHits); 188 189 AttributesImpl atts = new AttributesImpl(); 190 atts.addCDATAAttribute("total", String.valueOf(nbPages)); // Number of pages 191 atts.addCDATAAttribute("current", String.valueOf(currentPage)); // Current page 192 atts.addCDATAAttribute("start", String.valueOf(start)); // Index of the first hit 193 194 // Index of the last hit ( 195 // when no result start=0 and end=0, 196 // when 1 result start=0 and end=1, 197 // when 2 results start=0 and end=2 etc. 198 // ) 199 int endIndexExclusive = pagination.currentEndDocExclusiveIndex(totalHits); 200 atts.addCDATAAttribute("end", String.valueOf(endIndexExclusive)); 201 202 XMLUtils.createElement(contentHandler, "pagination", atts); 203 } 204}