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}