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