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 090 _searchHelper = (ContentSearchHelper) manager.lookup(ContentSearchHelper.ROLE); 091 _searchModelHelper = (SearchModelHelper) manager.lookup(SearchModelHelper.ROLE); 092 } 093 094 /** 095 * Create a ContentSearcher from a search model. 096 * @param searchModel The reference search model. 097 * @return a ContentSearcher backed by the given search model. 098 */ 099 public SearchModelContentSearcher create(SearchModel searchModel) 100 { 101 return new SearchModelContentSearcher(searchModel); 102 } 103 104 /** 105 * Create a simple ContentSearcher from a list of content types. 106 * @param contentTypes The content types to search on. 107 * @return a ContentSearcher referencing the given content types. 108 */ 109 public SimpleContentSearcher create(String... contentTypes) 110 { 111 return new SimpleContentSearcher(Arrays.asList(contentTypes)); 112 } 113 114 /** 115 * Create a simple ContentSearcher from a list of content types. 116 * @param contentTypes The content types to search on. 117 * @return a ContentSearcher referencing the given content types. 118 */ 119 public SimpleContentSearcher create(Collection<String> contentTypes) 120 { 121 return new SimpleContentSearcher(contentTypes); 122 } 123 124 /** 125 * A ContentSearcher backed by a {@link SearchModel}. 126 */ 127 public class SearchModelContentSearcher 128 { 129 private SearchModel _searchModel; 130 private List<ContentSearchSort> _sort; 131 private String _searchMode; 132 private int _start; 133 private int _maxResults; 134 private boolean _checkRights; 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 * Whether to check rights when searching, false otherwise. 199 * @param checkRights <code>true</code> to check rights, <code>false</code> otherwise. 200 * @return The ContentSearcher itself. 201 */ 202 public SearchModelContentSearcher setCheckRights(boolean checkRights) 203 { 204 _checkRights = checkRights; 205 return this; 206 } 207 208 /** 209 * Search the contents. 210 * @param values The values for criteria defined in the model. 211 * @param <C> The type Content 212 * @return The search results as {@link AmetysObject}s. 213 * @throws Exception if an error occurs. 214 */ 215 public <C extends Content> AmetysObjectIterable<C> search(Map<String, Object> values) throws Exception 216 { 217 return _searcher(values, Collections.emptyMap(), Collections.emptyMap()).search(); 218 } 219 220 /** 221 * Search the contents. 222 * @param values The values for criteria defined in the model. 223 * @param contextualParameters The search contextual parameters. 224 * @param <C> The type Content 225 * @return The search results as {@link AmetysObject}s. 226 * @throws Exception if an error occurs. 227 */ 228 public <C extends Content> AmetysObjectIterable<C> search(Map<String, Object> values, Map<String, Object> contextualParameters) throws Exception 229 { 230 return _searcher(values, Collections.emptyMap(), contextualParameters).search(); 231 } 232 233 /** 234 * Search the contents. 235 * @param values The values for criteria defined in the model. 236 * @param <C> The type Content 237 * @return The search results. 238 * @throws Exception if an error occurs. 239 */ 240 public <C extends Content> SearchResults<C> searchWithFacets(Map<String, Object> values) throws Exception 241 { 242 return searchWithFacets(values, Collections.emptyMap()); 243 } 244 245 /** 246 * Search the contents. 247 * @param <C> The type Content 248 * @param values The values for criteria defined in the model. 249 * @param contextualParameters The search contextual parameters. 250 * @return The search results. 251 * @throws Exception if an error occurs. 252 */ 253 public <C extends Content> SearchResults<C> searchWithFacets(Map<String, Object> values, Map<String, Object> contextualParameters) throws Exception 254 { 255 return searchWithFacets(values, Collections.emptyMap(), contextualParameters); 256 } 257 258 /** 259 * Search the contents. 260 * @param <C> The type Content 261 * @param values The values for criteria defined in the model. 262 * @param facetValues The facet values, indexed 263 * @param contextualParameters The search contextual parameters. 264 * @return The search results. 265 * @throws Exception if an error occurs. 266 */ 267 public <C extends Content> SearchResults<C> searchWithFacets(Map<String, Object> values, Map<String, List<String>> facetValues, Map<String, Object> contextualParameters) throws Exception 268 { 269 return _searcher(values, facetValues, contextualParameters).searchWithFacets(); 270 } 271 272 private Searcher _searcher(Map<String, Object> values, Map<String, List<String>> facetValues, Map<String, Object> contextualParameters) 273 { 274 Query query = _queryBuilder.build(_searchModel, _searchMode, values, contextualParameters); 275 276 Set<String> contentTypeIds = _searchModel.getContentTypes(contextualParameters); 277 List<SortDefinition> sort = _searchHelper.transformContentSearcherSorts(_sort, contentTypeIds); 278 279 List<FacetDefinition> facets = _searchHelper.getFacetDefinitions(_searchModel, contextualParameters); 280 281 return _searcherFactory.create() 282 .withQuery(query) 283 .withFilterQueries(new DocumentTypeQuery(SolrFieldNames.TYPE_CONTENT)) 284 .withSort(sort) 285 .withFacets(facets) 286 .withFacetValues(facetValues) 287 .withLimits(_start, _maxResults) 288 .setCheckRights(_checkRights); 289 } 290 } 291 292 /** 293 * A ContentSearcher on a list of content types. 294 */ 295 public class SimpleContentSearcher 296 { 297 298 private Set<String> _contentTypeIds; 299 private List<ContentSearchSort> _sort; 300 private List<String> _facets; 301 private int _start; 302 private int _maxResults; 303 private boolean _checkRights; 304 private List<String> _filterQueryStrings; 305 private List<Query> _filterQueries; 306 307 /** 308 * Build a content searcher on a list of content types. 309 * @param contentTypes A collection of content types to search on. 310 */ 311 public SimpleContentSearcher(Collection<String> contentTypes) 312 { 313 this._contentTypeIds = contentTypes != null ? new HashSet<>(contentTypes) : Collections.emptySet(); 314 this._sort = new ArrayList<>(); 315 this._facets = new ArrayList<>(); 316 this._start = 0; 317 this._maxResults = Integer.MAX_VALUE; 318 this._checkRights = true; 319 } 320 321 /** 322 * Set the filter queries. 323 * @param filterQueries the filter queries. 324 * @return The ContentSearcher itself. 325 */ 326 public SimpleContentSearcher withFilterQueries(List<Query> filterQueries) 327 { 328 _filterQueries = filterQueries; 329 return this; 330 } 331 332 /** 333 * Set the filter queries. 334 * @param filterQueryStrings the filter queries. 335 * @return The ContentSearcher itself. 336 */ 337 public SimpleContentSearcher withFilterQueryStrings(List<String> filterQueryStrings) 338 { 339 _filterQueryStrings = filterQueryStrings; 340 return this; 341 } 342 343 /** 344 * Set the sort criteria. 345 * @param sortCriteria The sort criteria as a List. 346 * @return The ContentSearcher itself. 347 */ 348 public SimpleContentSearcher withSort(List<ContentSearchSort> sortCriteria) 349 { 350 _sort = new ArrayList<>(sortCriteria); 351 return this; 352 } 353 354 /** 355 * Add a sort criterion. 356 * @param fieldRef The field reference (name of a SearchField). 357 * @param order The sort order. 358 * @return The ContentSearcher itself. 359 */ 360 public SimpleContentSearcher addSort(String fieldRef, SortOrder order) 361 { 362 _sort.add(new ContentSearchSort(fieldRef, order)); 363 return this; 364 } 365 366 /** 367 * Set the facets. 368 * @param facets The facets list. 369 * @return The ContentSearcher itself. 370 */ 371 public SimpleContentSearcher withFacets(Collection<String> facets) 372 { 373 _facets = new ArrayList<>(facets); 374 return this; 375 } 376 377 /** 378 * Set the facets. 379 * @param facets The facets list. 380 * @return The ContentSearcher itself. 381 */ 382 public SimpleContentSearcher withFacets(String... facets) 383 { 384 _facets = Arrays.asList(facets); 385 return this; 386 } 387 388 /** 389 * Set the limits to use. 390 * @param start The start index. 391 * @param maxResults The maximum number of results. 392 * @return The ContentSearcher itself. 393 */ 394 public SimpleContentSearcher withLimits(int start, int maxResults) 395 { 396 this._start = start; 397 this._maxResults = maxResults; 398 return this; 399 } 400 401 /** 402 * Whether to check rights when searching, false otherwise. 403 * @param checkRights <code>true</code> to check rights, <code>false</code> otherwise. 404 * @return The ContentSearcher itself. 405 */ 406 public SimpleContentSearcher setCheckRights(boolean checkRights) 407 { 408 _checkRights = checkRights; 409 return this; 410 } 411 412 /** 413 * Search the contents. 414 * @param <C> The type Content 415 * @param query The query object to execute. 416 * @return The search results as {@link AmetysObject}s. 417 * @throws Exception if an error occurs. 418 */ 419 public <C extends Content> AmetysObjectIterable<C> search(Query query) throws Exception 420 { 421 return _searcher(query, Collections.emptyMap()).search(); 422 } 423 424 /** 425 * Search the contents. 426 * @param <C> The type Content 427 * @param query The query string to execute. 428 * @return The search results as {@link AmetysObject}s. 429 * @throws Exception if an error occurs. 430 */ 431 public <C extends Content> AmetysObjectIterable<C> search(String query) throws Exception 432 { 433 return _searcher(query, Collections.emptyMap()).search(); 434 } 435 436 /** 437 * Search the contents. 438 * @param <C> The type Content 439 * @param query The query objet to execute. 440 * @return The search results. 441 * @throws Exception if an error occurs. 442 */ 443 public <C extends Content> SearchResults<C> searchWithFacets(Query query) throws Exception 444 { 445 return searchWithFacets(query, Collections.emptyMap()); 446 } 447 448 /** 449 * Search the contents. 450 * @param <C> The type Content 451 * @param query The query string to execute. 452 * @return The search results. 453 * @throws Exception if an error occurs. 454 */ 455 public <C extends Content> SearchResults<C> searchWithFacets(String query) throws Exception 456 { 457 return searchWithFacets(query, Collections.emptyMap()); 458 } 459 460 /** 461 * Search the contents. 462 * @param <C> The type Content 463 * @param query The query object to execute. 464 * @param facetValues The facet values. 465 * @return The search results. 466 * @throws Exception if an error occurs. 467 */ 468 public <C extends Content> SearchResults<C> searchWithFacets(Query query, Map<String, List<String>> facetValues) throws Exception 469 { 470 return _searcher(query, facetValues).searchWithFacets(); 471 } 472 473 /** 474 * Search the contents. 475 * @param <C> The type Content 476 * @param query The query string to execute. 477 * @param facetValues The facet values. 478 * @return The search results. 479 * @throws Exception if an error occurs. 480 */ 481 public <C extends Content> SearchResults<C> searchWithFacets(String query, Map<String, List<String>> facetValues) throws Exception 482 { 483 return _searcher(query, facetValues).searchWithFacets(); 484 } 485 486 private Searcher _searcher(String query, Map<String, List<String>> facetValues) 487 { 488 return _searcher(facetValues).withQueryString(query); 489 } 490 491 private Searcher _searcher(Query query, Map<String, List<String>> facetValues) 492 { 493 return _searcher(facetValues).withQuery(query); 494 } 495 496 private Searcher _searcher(Map<String, List<String>> facetValues) 497 { 498 Set<String> commonContentTypeIds = _contentTypesHelper.getCommonAncestors(_contentTypeIds); 499 List<SortDefinition> sort = _searchHelper.transformContentSearcherSorts(_sort, commonContentTypeIds); 500 List<FacetDefinition> facets = _searchHelper.getFacetDefinitions(_facets, commonContentTypeIds); 501 502 List<Query> filterQueries = new ArrayList<>(); 503 filterQueries.add(new DocumentTypeQuery(SolrFieldNames.TYPE_CONTENT)); 504 505 if (!_contentTypeIds.isEmpty()) 506 { 507 filterQueries.add(_searchModelHelper.createContentTypeOrMixinQuery(_contentTypeIds)); 508 } 509 510 if (_filterQueries != null) 511 { 512 filterQueries.addAll(_filterQueries); 513 } 514 515 List<String> filterQueryStrings = new ArrayList<>(); 516 517 if (_filterQueryStrings != null) 518 { 519 filterQueryStrings.addAll(_filterQueryStrings); 520 } 521 522 return _searcherFactory.create() 523 .withFilterQueries(filterQueries) 524 .withFilterQueryStrings(filterQueryStrings) 525 .withSort(sort) 526 .withFacets(facets) 527 .withFacetValues(facetValues) 528 .withLimits(_start, _maxResults) 529 .setCheckRights(_checkRights); 530 } 531 } 532 533 /** 534 * Record representing a sort criterion. 535 * @param sortField The sort field. Can be a path to an element 536 * @param order The sort order 537 */ 538 public record ContentSearchSort (String sortField, SortOrder order) { /* empty */ } 539}