001/* 002 * Copyright 2017 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.plugins.odfweb.program; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.List; 023import java.util.Optional; 024import java.util.stream.Collectors; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.ProcessingException; 029import org.apache.cocoon.environment.Request; 030import org.apache.cocoon.xml.AttributesImpl; 031import org.apache.cocoon.xml.XMLUtils; 032import org.apache.commons.lang3.StringUtils; 033import org.xml.sax.SAXException; 034 035import org.ametys.cms.content.indexing.solr.SolrFieldNames; 036import org.ametys.cms.contenttype.ContentType; 037import org.ametys.cms.search.SearchResults; 038import org.ametys.cms.search.query.AndQuery; 039import org.ametys.cms.search.query.ConstantNilScoreQuery; 040import org.ametys.cms.search.query.ContentTypeQuery; 041import org.ametys.cms.search.query.DocumentTypeQuery; 042import org.ametys.cms.search.query.JoinQuery; 043import org.ametys.cms.search.query.MatchAllQuery; 044import org.ametys.cms.search.query.MaxScoreOrQuery; 045import org.ametys.cms.search.query.OrQuery; 046import org.ametys.cms.search.query.Query; 047import org.ametys.cms.search.query.StringQuery; 048import org.ametys.cms.search.solr.SearcherFactory.Searcher; 049import org.ametys.odf.ProgramItem; 050import org.ametys.odf.program.AbstractProgram; 051import org.ametys.odf.program.ProgramFactory; 052import org.ametys.odf.program.ProgramPart; 053import org.ametys.odf.program.SubProgram; 054import org.ametys.odf.program.SubProgramFactory; 055import org.ametys.odf.program.TraversableProgramPart; 056import org.ametys.odf.skill.ProgramSkillsIndexingField; 057import org.ametys.plugins.odfweb.repository.OdfPageResolver; 058import org.ametys.plugins.odfweb.repository.ProgramPage; 059import org.ametys.plugins.repository.AmetysObject; 060import org.ametys.plugins.repository.AmetysObjectIterable; 061import org.ametys.runtime.model.View; 062import org.ametys.web.frontoffice.SearchGenerator; 063import org.ametys.web.indexing.solr.SolrWebFieldNames; 064import org.ametys.web.repository.page.Page; 065import org.ametys.web.search.query.PageContentQuery; 066 067/** 068 * ODF search results 069 */ 070public class FrontODFSearch extends SearchGenerator 071{ 072 /** 073 * Enumeration for display subprogram mode 074 */ 075 public enum DisplaySubprogramMode 076 { 077 /** Display no subprogram */ 078 NONE, 079 /** Display all subprograms */ 080 ALL, 081 /** Display all subprograms with highlighting those which match the search criteria*/ 082 ALL_WITH_HIGHLIGHT, 083 /** Display matching subprograms only */ 084 MATCHING_SEARCH_ONLY 085 } 086 087 /** The matching subprograms */ 088 protected List<String> _matchingSubProgramIds; 089 090 private DisplaySubprogramMode _displaySubprogramMode; 091 private OdfPageResolver _odfPageResolver; 092 093 @Override 094 public void service(ServiceManager smanager) throws ServiceException 095 { 096 super.service(smanager); 097 _odfPageResolver = (OdfPageResolver) smanager.lookup(OdfPageResolver.ROLE); 098 } 099 100 @Override 101 protected Collection<String> getContentTypes(Request request) 102 { 103 return Arrays.asList(parameters.getParameter("contentType", ProgramFactory.PROGRAM_CONTENT_TYPE)); 104 } 105 106 @Override 107 public void generate() throws IOException, SAXException, ProcessingException 108 { 109 _displaySubprogramMode = null; 110 _matchingSubProgramIds = new ArrayList<>(); 111 112 super.generate(); 113 } 114 115 @Override 116 protected List<Query> getContentQueries(Request request, Collection<String> siteNames, String language) 117 { 118 List<Query> contentQueries = super.getContentQueries(request, siteNames, language); 119 120 // Add catalog query 121 String catalog = parameters.getParameter("catalog", request.getParameter("catalog")); 122 contentQueries.add(new ConstantNilScoreQuery(new StringQuery(ProgramItem.CATALOG, catalog))); 123 124 // Add query on acquired skill's id if present 125 String skillId = request.getParameter("skillId"); 126 if (StringUtils.isNotBlank(skillId)) 127 { 128 contentQueries.add(new StringQuery(ProgramSkillsIndexingField.PROGRAM_SKILLS_INDEXING_FIELD, skillId)); 129 } 130 131 return contentQueries; 132 } 133 134 @Override 135 protected Query getQuery(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException 136 { 137 List<Query> finalQueries = new ArrayList<>(); 138 139 List<Query> wordingQueries = getWordingQueries(request, siteNames, language); 140 Query wordingQuery = wordingQueries.isEmpty() ? new MatchAllQuery() : new AndQuery(wordingQueries); 141 142 // Query to execute on pages 143 List<Query> pagesQueries = new ArrayList<>(); 144 pagesQueries.addAll(getPageQueries(request, siteNames, language)); // add specific queries to pages 145 146 if (!pagesQueries.isEmpty()) 147 { 148 pagesQueries.add(wordingQuery); // add wording queries 149 finalQueries.add(new AndQuery(pagesQueries)); 150 } 151 152 // Query to execute on joined contents 153 List<Query> contentQueries = new ArrayList<>(wordingQueries); // add wording queries 154 contentQueries.addAll(getContentQueries(request, siteNames, language)); // add specific queries to contents 155 Query contentQuery = new AndQuery(contentQueries); 156 157 List<Query> contentOrResourcesQueries = new ArrayList<>(); 158 contentOrResourcesQueries.add(contentQuery); 159 contentOrResourcesQueries.addAll(getContentResourcesOrAttachmentQueries(wordingQuery)); // add queries on join content's resources 160 161 Query programContentQuery = new OrQuery(contentOrResourcesQueries); 162 Query programPageQuery = new PageContentQuery(programContentQuery); 163 164 finalQueries.add(programPageQuery); 165 166 Query subProgramPageQuery = null; 167 if (_searchOnSubPrograms()) 168 { 169 subProgramPageQuery = getSubProgramPageQuery(programContentQuery); // add query on joined subprograms 170 } 171 172 finalQueries.add(subProgramPageQuery); 173 174 return finalQueries.isEmpty() ? new MatchAllQuery() : new MaxScoreOrQuery(finalQueries); 175 } 176 177 @Override 178 protected SearchResults<AmetysObject> search(Request request, Collection<String> siteNames, String language, int pageIndex, int start, int maxResults, boolean saxResults) 179 throws Exception 180 { 181 _matchingSubProgramIds = new ArrayList<>(); 182 if (saxResults) 183 { 184 DisplaySubprogramMode displaySubProgramMode = getDisplaySubProgramMode(); 185 if (displaySubProgramMode.equals(DisplaySubprogramMode.ALL_WITH_HIGHLIGHT) || displaySubProgramMode.equals(DisplaySubprogramMode.MATCHING_SEARCH_ONLY)) 186 { 187 _matchingSubProgramIds = getSubProgramsMatchingSearch(request, siteNames, language); 188 } 189 } 190 191 return super.search(request, siteNames, language, pageIndex, start, maxResults, saxResults); 192 } 193 194 /** 195 * Get the ids of subprograms matching the current search 196 * @param request The request 197 * @param siteNames The site names 198 * @param language The languages 199 * @return the ids of matching subprograms 200 * @throws Exception if failed to execute search 201 */ 202 protected List<String> getSubProgramsMatchingSearch(Request request, Collection<String> siteNames, String language) throws Exception 203 { 204 List<Query> wordingQueries = getWordingQueries(request, siteNames, language); 205 Query wordingQuery = wordingQueries.isEmpty() ? new MatchAllQuery() : new AndQuery(wordingQueries); 206 207 // Query to execute on joined contents 208 List<Query> contentQueries = new ArrayList<>(wordingQueries); // add wording queries 209 contentQueries.addAll(getContentQueries(request, siteNames, language)); // add specific queries to contents 210 contentQueries.add(new ContentTypeQuery(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE)); 211 Query contentQuery = new AndQuery(contentQueries); 212 213 List<Query> contentOrResourcesQueries = new ArrayList<>(); 214 contentOrResourcesQueries.add(contentQuery); 215 contentOrResourcesQueries.addAll(getContentResourcesOrAttachmentQueries(wordingQuery)); // add queries on join content's resources 216 217 Searcher searcher = _searcherFactory.create() 218 .withQuery(new OrQuery(contentOrResourcesQueries)) 219 .addFilterQuery(new DocumentTypeQuery(SolrFieldNames.TYPE_CONTENT)) 220 .withLimits(0, Integer.MAX_VALUE) 221 .setCheckRights(_checkRights()); 222 223 AmetysObjectIterable<AmetysObject> subPrograms = searcher.search(); 224 return subPrograms.stream().map(ao -> ao.getId()).collect(Collectors.toList()); 225 } 226 227 /** 228 * Get the page query to execute for subprogram's pages 229 * @param contentQuery the initial content query 230 * @return the page query for subprogram 231 */ 232 protected Query getSubProgramPageQuery(Query contentQuery) 233 { 234 Query subProgramTypeQuery = new ConstantNilScoreQuery(new ContentTypeQuery(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE)); 235 Query subProgramContentQuery = new AndQuery(subProgramTypeQuery, contentQuery); 236 return new SubProgramPageContentQuery(subProgramContentQuery); 237 } 238 239 @Override 240 protected void saxAdditionalInfosOnPageHit(Page page) throws SAXException 241 { 242 super.saxAdditionalInfosOnPageHit(page); 243 244 DisplaySubprogramMode displaySubProgramMode = getDisplaySubProgramMode(); 245 246 if (displaySubProgramMode != DisplaySubprogramMode.NONE && page instanceof ProgramPage) 247 { 248 ContentType contentType = _cTypeExtPt.getExtension(SubProgramFactory.SUBPROGRAM_CONTENT_TYPE); 249 View view = Optional.ofNullable(contentType).map(cType -> cType.getView("index")).orElse(null); 250 251 if (page instanceof ProgramPage) 252 { 253 String programPath = page.getPathInSitemap(); 254 AbstractProgram<ProgramFactory> program = ((ProgramPage) page).getProgram(); 255 for (ProgramPart childProgramPart : program.getProgramPartChildren()) 256 { 257 if (childProgramPart instanceof SubProgram) 258 { 259 SubProgram subProgram = (SubProgram) childProgramPart; 260 261 boolean matchSearch = _matchingSubProgramIds.contains(subProgram.getId()); 262 if (!_displaySubprogramMode.equals(DisplaySubprogramMode.MATCHING_SEARCH_ONLY) || matchSearch) 263 { 264 AttributesImpl attrs = new AttributesImpl(); 265 Page subProgramPage = _odfPageResolver.getSubProgramPage(subProgram, program, page.getSiteName()); 266 if (subProgramPage != null) 267 { 268 attrs.addCDATAAttribute("path", StringUtils.substringAfterLast(subProgramPage.getPathInSitemap(), programPath)); 269 } 270 else 271 { 272 getLogger().warn("The subprogram '" + subProgram.getId() + "' was returned from the search but its virtual page could not be resolved"); 273 } 274 attrs.addCDATAAttribute("title", subProgram.getTitle()); 275 if (_displaySubprogramMode == DisplaySubprogramMode.ALL_WITH_HIGHLIGHT) 276 { 277 attrs.addCDATAAttribute("highlight", String.valueOf(matchSearch)); 278 } 279 XMLUtils.startElement(contentHandler, "subprogram", attrs); 280 281 try 282 { 283 subProgram.dataToSAX(contentHandler, view); 284 } 285 catch (Exception e) 286 { 287 getLogger().error("An error occurred during saxing subprogram '" + subProgram.getId() + "' metadata", e); 288 } 289 290 XMLUtils.endElement(contentHandler, "subprogram"); 291 } 292 } 293 } 294 } 295 } 296 } 297 298 /** 299 * Get the display mode for subprograms 300 * @return the display mode 301 */ 302 protected DisplaySubprogramMode getDisplaySubProgramMode() 303 { 304 if (_displaySubprogramMode == null) 305 { 306 String displaySubprogramsParam = parameters.getParameter("display-subprograms", "none"); 307 _displaySubprogramMode = DisplaySubprogramMode.valueOf(displaySubprogramsParam.toUpperCase()); 308 } 309 return _displaySubprogramMode; 310 311 } 312 /** 313 * Determines the search should be executed on subprograms 314 * @return true to execute search also on subprograms 315 */ 316 protected boolean _searchOnSubPrograms() 317 { 318 return parameters.getParameterAsBoolean("subprogram-search", false); 319 } 320 321 @Override 322 protected void addContentTypeQuery(Collection<Query> queries, Request request) 323 { 324 Collection<String> cTypes = new ArrayList<>(getContentTypes(request)); 325 queries.add(new PageContentQuery(new ContentTypeQuery(cTypes))); 326 } 327 328 class SubProgramPageContentQuery extends JoinQuery 329 { 330 public SubProgramPageContentQuery(Query subQuery) 331 { 332 super(subQuery, SolrWebFieldNames.CONTENT_IDS, TraversableProgramPart.CHILD_PROGRAM_PARTS); 333 } 334 } 335}