001/* 002 * Copyright 2016 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.cms.search.content; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031 032import org.ametys.cms.content.indexing.solr.SolrFieldNames; 033import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 034import org.ametys.cms.contenttype.ContentTypesHelper; 035import org.ametys.cms.repository.Content; 036import org.ametys.cms.search.QueryBuilder; 037import org.ametys.cms.search.SearchResults; 038import org.ametys.cms.search.SortOrder; 039import org.ametys.cms.search.model.SearchModel; 040import org.ametys.cms.search.model.SearchModelHelper; 041import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 042import org.ametys.cms.search.query.DocumentTypeQuery; 043import org.ametys.cms.search.query.Query; 044import org.ametys.cms.search.solr.SearcherFactory; 045import org.ametys.cms.search.solr.SearcherFactory.FacetDefinition; 046import org.ametys.cms.search.solr.SearcherFactory.Searcher; 047import org.ametys.cms.search.solr.SearcherFactory.SortDefinition; 048import org.ametys.plugins.repository.AmetysObject; 049import org.ametys.plugins.repository.AmetysObjectIterable; 050import org.ametys.runtime.plugin.component.AbstractLogEnabled; 051 052/** 053 * Component creating content searchers from {@link SearchModel}s or content type IDs. 054 */ 055public class ContentSearcherFactory extends AbstractLogEnabled implements Component, Serviceable 056{ 057 /** The component role. */ 058 public static final String ROLE = ContentSearcherFactory.class.getName(); 059 060 /** The searcher factory. */ 061 protected SearcherFactory _searcherFactory; 062 063 /** The query builder. */ 064 protected QueryBuilder _queryBuilder; 065 066 /** The content type extension point. */ 067 protected ContentTypeExtensionPoint _cTypeEP; 068 069 /** The content type helper. */ 070 protected ContentTypesHelper _contentTypesHelper; 071 072 /** The system property extension point. */ 073 protected SystemPropertyExtensionPoint _sysPropEP; 074 075 /** The search helper. */ 076 protected ContentSearchHelper _searchHelper; 077 078 /** The search model helper */ 079 protected SearchModelHelper _searchModelHelper; 080 081 @Override 082 public void service(ServiceManager manager) throws ServiceException 083 { 084 _searcherFactory = (SearcherFactory) manager.lookup(SearcherFactory.ROLE); 085 _queryBuilder = (QueryBuilder) manager.lookup(QueryBuilder.ROLE); 086 _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 087 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 088 _sysPropEP = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 089 _searchHelper = (ContentSearchHelper) manager.lookup(ContentSearchHelper.ROLE); 090 _searchModelHelper = (SearchModelHelper) manager.lookup(SearchModelHelper.ROLE); 091 } 092 093 /** 094 * Create a ContentSearcher from a search model. 095 * @param searchModel The reference search model. 096 * @return a ContentSearcher backed by the given search model. 097 */ 098 public SearchModelContentSearcher create(SearchModel searchModel) 099 { 100 return new SearchModelContentSearcher(searchModel); 101 } 102 103 /** 104 * Create a simple ContentSearcher from a list of content types. 105 * @param contentTypes The content types to search on. 106 * @return a ContentSearcher referencing the given content types. 107 */ 108 public SimpleContentSearcher create(String... contentTypes) 109 { 110 return new SimpleContentSearcher(Arrays.asList(contentTypes)); 111 } 112 113 /** 114 * Create a simple ContentSearcher from a list of content types. 115 * @param contentTypes The content types to search on. 116 * @return a ContentSearcher referencing the given content types. 117 */ 118 public SimpleContentSearcher create(Collection<String> contentTypes) 119 { 120 return new SimpleContentSearcher(contentTypes); 121 } 122 123 /** 124 * A ContentSearcher backed by a {@link SearchModel}. 125 */ 126 public class SearchModelContentSearcher 127 { 128 private SearchModel _searchModel; 129 private List<ContentSearchSort> _sort; 130 private String _searchMode; 131 private int _start; 132 private int _maxResults; 133 private boolean _checkRights; 134 private Query _highlightingQuery; 135 136 /** 137 * Build a ContentSearcher referencing a {@link SearchModel}. 138 * @param searchModel the {@link SearchModel}. 139 */ 140 public SearchModelContentSearcher(SearchModel searchModel) 141 { 142 this._searchModel = searchModel; 143 this._sort = new ArrayList<>(); 144 this._searchMode = "simple"; 145 this._start = 0; 146 this._maxResults = Integer.MAX_VALUE; 147 this._checkRights = true; 148 } 149 150 /** 151 * Add a sort criterion. 152 * @param fieldRef The field reference (name of a SearchField). 153 * @param order The sort order. 154 * @return The ContentSearcher itself. 155 */ 156 public SearchModelContentSearcher addSort(String fieldRef, SortOrder order) 157 { 158 _sort.add(new ContentSearchSort(fieldRef, order)); 159 return this; 160 } 161 162 /** 163 * Set the sort criteria. 164 * @param sortCriteria The sort criteria as a List. 165 * @return The ContentSearcher itself. 166 */ 167 public SearchModelContentSearcher withSort(List<ContentSearchSort> sortCriteria) 168 { 169 _sort = new ArrayList<>(sortCriteria); 170 return this; 171 } 172 173 /** 174 * Set the search mode. 175 * @param searchMode The search mode. 176 * @return The ContentSearcher itself. 177 */ 178 public SearchModelContentSearcher withSearchMode(String searchMode) 179 { 180 _searchMode = searchMode; 181 return this; 182 } 183 184 /** 185 * Set the limits to use. 186 * @param start The start index. 187 * @param maxResults The maximum number of results. 188 * @return The ContentSearcher itself. 189 */ 190 public SearchModelContentSearcher withLimits(int start, int maxResults) 191 { 192 this._start = start; 193 this._maxResults = maxResults; 194 return this; 195 } 196 197 /** 198 * Set the highlight to use. 199 * @param query the highlighting query 200 * @return The ContentSearcher itself. 201 */ 202 public SearchModelContentSearcher withHighlightingQuery(Query query) 203 { 204 this._highlightingQuery = query; 205 return this; 206 } 207 208 /** 209 * Whether to check rights when searching, false otherwise. 210 * @param checkRights <code>true</code> to check rights, <code>false</code> otherwise. 211 * @return The ContentSearcher itself. 212 */ 213 public SearchModelContentSearcher setCheckRights(boolean checkRights) 214 { 215 _checkRights = checkRights; 216 return this; 217 } 218 219 /** 220 * Search the contents. 221 * @param values The values for criteria defined in the model. 222 * @param <C> The type Content 223 * @return The search results as {@link AmetysObject}s. 224 * @throws Exception if an error occurs. 225 */ 226 public <C extends Content> AmetysObjectIterable<C> search(Map<String, Object> values) throws Exception 227 { 228 return _searcher(values, Collections.emptyMap(), Collections.emptyMap()).search(); 229 } 230 231 /** 232 * Search the contents. 233 * @param values The values for criteria defined in the model. 234 * @param contextualParameters The search contextual parameters. 235 * @param <C> The type Content 236 * @return The search results as {@link AmetysObject}s. 237 * @throws Exception if an error occurs. 238 */ 239 public <C extends Content> AmetysObjectIterable<C> search(Map<String, Object> values, Map<String, Object> contextualParameters) throws Exception 240 { 241 return _searcher(values, Collections.emptyMap(), contextualParameters).search(); 242 } 243 244 /** 245 * Search the contents. 246 * @param values The values for criteria defined in the model. 247 * @param <C> The type Content 248 * @return The search results. 249 * @throws Exception if an error occurs. 250 */ 251 public <C extends Content> SearchResults<C> searchWithFacets(Map<String, Object> values) throws Exception 252 { 253 return searchWithFacets(values, Collections.emptyMap()); 254 } 255 256 /** 257 * Search the contents. 258 * @param <C> The type Content 259 * @param values The values for criteria defined in the model. 260 * @param contextualParameters The search contextual parameters. 261 * @return The search results. 262 * @throws Exception if an error occurs. 263 */ 264 public <C extends Content> SearchResults<C> searchWithFacets(Map<String, Object> values, Map<String, Object> contextualParameters) throws Exception 265 { 266 return searchWithFacets(values, Collections.emptyMap(), contextualParameters); 267 } 268 269 /** 270 * Search the contents. 271 * @param <C> The type Content 272 * @param values The values for criteria defined in the model. 273 * @param facetValues The facet values, indexed 274 * @param contextualParameters The search contextual parameters. 275 * @return The search results. 276 * @throws Exception if an error occurs. 277 */ 278 public <C extends Content> SearchResults<C> searchWithFacets(Map<String, Object> values, Map<String, List<String>> facetValues, Map<String, Object> contextualParameters) throws Exception 279 { 280 return _searcher(values, facetValues, contextualParameters).searchWithFacets(); 281 } 282 283 private Searcher _searcher(Map<String, Object> values, Map<String, List<String>> facetValues, Map<String, Object> contextualParameters) 284 { 285 Query query = _queryBuilder.build(_searchModel, _searchMode, values, contextualParameters); 286 287 Set<String> contentTypeIds = _searchModel.getContentTypes(contextualParameters); 288 List<SortDefinition> sort = _searchHelper.transformContentSearcherSorts(_sort, contentTypeIds); 289 290 List<FacetDefinition> facets = _searchHelper.getFacetDefinitions(_searchModel, contextualParameters); 291 292 Searcher searcher = _searcherFactory.create() 293 .withQuery(query) 294 .withFilterQueries(new DocumentTypeQuery(SolrFieldNames.TYPE_CONTENT)) 295 .withSort(sort) 296 .withFacets(facets) 297 .withFacetValues(facetValues) 298 .withLimits(_start, _maxResults) 299 .setCheckRights(_checkRights) 300 .withHighlightingQuery(_highlightingQuery); 301 302 return searcher; 303 } 304 } 305 306 /** 307 * A ContentSearcher on a list of content types. 308 */ 309 public class SimpleContentSearcher 310 { 311 312 private Set<String> _contentTypeIds; 313 private List<ContentSearchSort> _sort; 314 private List<String> _facets; 315 private int _start; 316 private int _maxResults; 317 private boolean _checkRights; 318 private List<String> _filterQueryStrings; 319 private List<Query> _filterQueries; 320 private Query _highlightingQuery; 321 322 /** 323 * Build a content searcher on a list of content types. 324 * @param contentTypes A collection of content types to search on. 325 */ 326 public SimpleContentSearcher(Collection<String> contentTypes) 327 { 328 this._contentTypeIds = contentTypes != null ? new HashSet<>(contentTypes) : Collections.emptySet(); 329 this._sort = new ArrayList<>(); 330 this._facets = new ArrayList<>(); 331 this._start = 0; 332 this._maxResults = Integer.MAX_VALUE; 333 this._checkRights = true; 334 } 335 336 /** 337 * Set the filter queries. 338 * @param filterQueries the filter queries. 339 * @return The ContentSearcher itself. 340 */ 341 public SimpleContentSearcher withFilterQueries(List<Query> filterQueries) 342 { 343 _filterQueries = filterQueries; 344 return this; 345 } 346 347 /** 348 * Set the filter queries. 349 * @param filterQueryStrings the filter queries. 350 * @return The ContentSearcher itself. 351 */ 352 public SimpleContentSearcher withFilterQueryStrings(List<String> filterQueryStrings) 353 { 354 _filterQueryStrings = filterQueryStrings; 355 return this; 356 } 357 358 /** 359 * Set the sort criteria. 360 * @param sortCriteria The sort criteria as a List. 361 * @return The ContentSearcher itself. 362 */ 363 public SimpleContentSearcher withSort(List<ContentSearchSort> sortCriteria) 364 { 365 _sort = new ArrayList<>(sortCriteria); 366 return this; 367 } 368 369 /** 370 * Add a sort criterion. 371 * @param fieldRef The field reference (name of a SearchField). 372 * @param order The sort order. 373 * @return The ContentSearcher itself. 374 */ 375 public SimpleContentSearcher addSort(String fieldRef, SortOrder order) 376 { 377 _sort.add(new ContentSearchSort(fieldRef, order)); 378 return this; 379 } 380 381 /** 382 * Set the facets. 383 * @param facets The facets list. 384 * @return The ContentSearcher itself. 385 */ 386 public SimpleContentSearcher withFacets(Collection<String> facets) 387 { 388 _facets = new ArrayList<>(facets); 389 return this; 390 } 391 392 /** 393 * Set the facets. 394 * @param facets The facets list. 395 * @return The ContentSearcher itself. 396 */ 397 public SimpleContentSearcher withFacets(String... facets) 398 { 399 _facets = Arrays.asList(facets); 400 return this; 401 } 402 403 /** 404 * Set the limits to use. 405 * @param start The start index. 406 * @param maxResults The maximum number of results. 407 * @return The ContentSearcher itself. 408 */ 409 public SimpleContentSearcher withLimits(int start, int maxResults) 410 { 411 this._start = start; 412 this._maxResults = maxResults; 413 return this; 414 } 415 416 /** 417 * Set the highlight to use. 418 * @param query the highlighting query 419 * @return The ContentSearcher itself. 420 */ 421 public SimpleContentSearcher withHighlightingQuery(Query query) 422 { 423 this._highlightingQuery = query; 424 return this; 425 } 426 427 /** 428 * Whether to check rights when searching, false otherwise. 429 * @param checkRights <code>true</code> to check rights, <code>false</code> otherwise. 430 * @return The ContentSearcher itself. 431 */ 432 public SimpleContentSearcher setCheckRights(boolean checkRights) 433 { 434 _checkRights = checkRights; 435 return this; 436 } 437 438 /** 439 * Search the contents. 440 * @param <C> The type Content 441 * @param query The query object to execute. 442 * @return The search results as {@link AmetysObject}s. 443 * @throws Exception if an error occurs. 444 */ 445 public <C extends Content> AmetysObjectIterable<C> search(Query query) throws Exception 446 { 447 return _searcher(query, Collections.emptyMap()).search(); 448 } 449 450 /** 451 * Search the contents. 452 * @param <C> The type Content 453 * @param query The query string to execute. 454 * @return The search results as {@link AmetysObject}s. 455 * @throws Exception if an error occurs. 456 */ 457 public <C extends Content> AmetysObjectIterable<C> search(String query) throws Exception 458 { 459 return _searcher(query, Collections.emptyMap()).search(); 460 } 461 462 /** 463 * Search the contents. 464 * @param <C> The type Content 465 * @param query The query object to execute. 466 * @return The search results. 467 * @throws Exception if an error occurs. 468 */ 469 public <C extends Content> SearchResults<C> searchWithFacets(Query query) throws Exception 470 { 471 return searchWithFacets(query, Collections.emptyMap()); 472 } 473 474 /** 475 * Search the contents. 476 * @param <C> The type Content 477 * @param query The query string to execute. 478 * @return The search results. 479 * @throws Exception if an error occurs. 480 */ 481 public <C extends Content> SearchResults<C> searchWithFacets(String query) throws Exception 482 { 483 return searchWithFacets(query, Collections.emptyMap()); 484 } 485 486 /** 487 * Search the contents. 488 * @param <C> The type Content 489 * @param query The query object to execute. 490 * @param facetValues The facet values. 491 * @return The search results. 492 * @throws Exception if an error occurs. 493 */ 494 public <C extends Content> SearchResults<C> searchWithFacets(Query query, Map<String, List<String>> facetValues) throws Exception 495 { 496 return _searcher(query, facetValues).searchWithFacets(); 497 } 498 499 /** 500 * Search the contents. 501 * @param <C> The type Content 502 * @param query The query string to execute. 503 * @param facetValues The facet values. 504 * @return The search results. 505 * @throws Exception if an error occurs. 506 */ 507 public <C extends Content> SearchResults<C> searchWithFacets(String query, Map<String, List<String>> facetValues) throws Exception 508 { 509 return _searcher(query, facetValues).searchWithFacets(); 510 } 511 512 private Searcher _searcher(String query, Map<String, List<String>> facetValues) 513 { 514 return _searcher(facetValues).withQueryString(query); 515 } 516 517 private Searcher _searcher(Query query, Map<String, List<String>> facetValues) 518 { 519 return _searcher(facetValues).withQuery(query); 520 } 521 522 private Searcher _searcher(Map<String, List<String>> facetValues) 523 { 524 Set<String> commonContentTypeIds = _contentTypesHelper.getCommonAncestors(_contentTypeIds); 525 List<SortDefinition> sort = _searchHelper.transformContentSearcherSorts(_sort, commonContentTypeIds); 526 List<FacetDefinition> facets = _searchHelper.getFacetDefinitions(_facets, commonContentTypeIds); 527 528 List<Query> filterQueries = new ArrayList<>(); 529 filterQueries.add(new DocumentTypeQuery(SolrFieldNames.TYPE_CONTENT)); 530 531 if (!_contentTypeIds.isEmpty()) 532 { 533 filterQueries.add(_searchModelHelper.createContentTypeOrMixinQuery(_contentTypeIds)); 534 } 535 536 if (_filterQueries != null) 537 { 538 filterQueries.addAll(_filterQueries); 539 } 540 541 List<String> filterQueryStrings = new ArrayList<>(); 542 543 if (_filterQueryStrings != null) 544 { 545 filterQueryStrings.addAll(_filterQueryStrings); 546 } 547 548 Searcher searcher = _searcherFactory.create() 549 .withFilterQueries(filterQueries) 550 .withFilterQueryStrings(filterQueryStrings) 551 .withSort(sort) 552 .withFacets(facets) 553 .withFacetValues(facetValues) 554 .withLimits(_start, _maxResults) 555 .setCheckRights(_checkRights) 556 .withHighlightingQuery(_highlightingQuery); 557 558 return searcher; 559 } 560 } 561 562 /** 563 * Record representing a sort criterion. 564 * @param sortField The sort field. Can be a path to an element 565 * @param order The sort order 566 */ 567 public record ContentSearchSort (String sortField, SortOrder order) { /* empty */ } 568}