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