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.solr; 017 018import java.net.URLDecoder; 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.activity.Initializable; 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.commons.collections.CollectionUtils; 034import org.apache.commons.lang3.StringUtils; 035import org.apache.solr.client.solrj.SolrClient; 036import org.apache.solr.client.solrj.SolrQuery; 037import org.apache.solr.client.solrj.SolrQuery.ORDER; 038import org.apache.solr.client.solrj.SolrRequest.METHOD; 039import org.apache.solr.client.solrj.response.FacetField; 040import org.apache.solr.client.solrj.response.FacetField.Count; 041import org.apache.solr.client.solrj.response.QueryResponse; 042import org.slf4j.Logger; 043 044import org.ametys.cms.search.SearchField; 045import org.ametys.cms.search.SearchResults; 046import org.ametys.cms.search.Sort; 047import org.ametys.cms.search.Sort.Order; 048import org.ametys.cms.search.query.Query; 049import org.ametys.cms.search.query.QuerySyntaxException; 050import org.ametys.core.group.GroupIdentity; 051import org.ametys.core.group.GroupManager; 052import org.ametys.core.right.AllowedUsers; 053import org.ametys.core.user.CurrentUserProvider; 054import org.ametys.core.user.UserIdentity; 055import org.ametys.plugins.repository.AmetysObject; 056import org.ametys.plugins.repository.AmetysObjectIterable; 057import org.ametys.plugins.repository.AmetysObjectResolver; 058import org.ametys.runtime.plugin.component.AbstractLogEnabled; 059 060/** 061 * Component searching objects corresponding to a {@link Query}. 062 */ 063public class SearcherFactory extends AbstractLogEnabled implements Component, Serviceable, Initializable 064{ 065 066 /** The component role. */ 067 public static final String ROLE = SearcherFactory.class.getName(); 068 069 /** The {@link AmetysObjectResolver} */ 070 protected AmetysObjectResolver _resolver; 071 072 /** The solr client provider */ 073 protected SolrClientProvider _solrClientProvider; 074 075 /** The current user provider. */ 076 protected CurrentUserProvider _currentUserProvider; 077 078 /** The group manager */ 079 protected GroupManager _groupManager; 080 081 /** The solr client */ 082 protected SolrClient _solrClient; 083 084 @Override 085 public void service(ServiceManager serviceManager) throws ServiceException 086 { 087 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 088 _solrClientProvider = (SolrClientProvider) serviceManager.lookup(SolrClientProvider.ROLE); 089 _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE); 090 _groupManager = (GroupManager) serviceManager.lookup(GroupManager.ROLE); 091 } 092 093 @Override 094 public void initialize() throws Exception 095 { 096 _solrClient = _solrClientProvider.getReadClient(); 097 } 098 099 /** 100 * Create a Searcher. 101 * @return a Searcher object. 102 */ 103 public Searcher create() 104 { 105 return new Searcher(getLogger()); 106 } 107 108 /** 109 * Class searching objects corresponding to a query, with optional sort, facets, and so on. 110 */ 111 public class Searcher 112 { 113 private Logger _logger; 114 115 private String _queryString; 116 private Query _query; 117 private List<String> _filterQueryStrings; 118 private List<Query> _filterQueries; 119 private List<Sort> _sortClauses; 120 private List<SearchField> _facets; 121 private Map<String, List<String>> _facetValues; 122 private int _start; 123 private int _maxResults; 124 private Map<String, Object> _searchContext; 125 private boolean _checkRights; 126 private AllowedUsers _checkRightsComparingTo; 127 private boolean _debug; 128 129 /** 130 * Build a Searcher with default values. 131 * @param logger The logger. 132 */ 133 protected Searcher(Logger logger) 134 { 135 _logger = logger; 136 137 _filterQueryStrings = new ArrayList<>(); 138 _filterQueries = new ArrayList<>(); 139 _sortClauses = new ArrayList<>(); 140 _facets = new ArrayList<>(); 141 _facetValues = new HashMap<>(); 142 _start = 0; 143 _maxResults = Integer.MAX_VALUE; 144 _searchContext = new HashMap<>(); 145 _checkRights = true; 146 } 147 148 /** 149 * Set the query (as a String). 150 * @param query the query (as a String). 151 * @return The Searcher object itself. 152 */ 153 public Searcher withQueryString(String query) 154 { 155 if (this._query != null) 156 { 157 throw new IllegalArgumentException("Query and query string can't be used at the same time."); 158 } 159 this._queryString = query; 160 return this; 161 } 162 163 /** 164 * Set the query (as a {@link Query} object). 165 * @param query the query (as a {@link Query} object). 166 * @return The Searcher object itself. 167 */ 168 public Searcher withQuery(Query query) 169 { 170 if (this._queryString != null) 171 { 172 throw new IllegalArgumentException("Query and query string can't be used at the same time."); 173 } 174 this._query = query; 175 return this; 176 } 177 178 /** 179 * Set the filter queries (as Strings). 180 * @param queries the filter queries (as Strings). 181 * @return The Searcher object itself. The Searcher object itself. 182 */ 183 public Searcher withFilterQueryStrings(String... queries) 184 { 185 _filterQueryStrings = new ArrayList<>(queries.length); 186 CollectionUtils.addAll(_filterQueryStrings, queries); 187 return this; 188 } 189 190 /** 191 * Set the filter queries (as Strings). 192 * @param queries the filter queries (as Strings). 193 * @return The Searcher object itself. The Searcher object itself. 194 */ 195 public Searcher withFilterQueryStrings(Collection<String> queries) 196 { 197 _filterQueryStrings = new ArrayList<>(queries); 198 return this; 199 } 200 201 /** 202 * Add a filter query to the existing ones (as a String). 203 * @param query the filter query to add (as a String). 204 * @return The Searcher object itself. The Searcher object itself. 205 */ 206 public Searcher addFilterQueryString(String query) 207 { 208 _filterQueryStrings.add(query); 209 return this; 210 } 211 212 /** 213 * Set the filter queries (as {@link Query} objects). 214 * @param queries the filter queries (as {@link Query} objects). 215 * @return The Searcher object itself. The Searcher object itself. 216 */ 217 public Searcher withFilterQueries(Query... queries) 218 { 219 _filterQueries = new ArrayList<>(queries.length); 220 CollectionUtils.addAll(_filterQueries, queries); 221 return this; 222 } 223 224 /** 225 * Set the filter queries (as {@link Query} objects). 226 * @param queries the filter queries (as {@link Query} objects). 227 * @return The Searcher object itself. The Searcher object itself. 228 */ 229 public Searcher withFilterQueries(Collection<Query> queries) 230 { 231 _filterQueries = new ArrayList<>(queries); 232 return this; 233 } 234 235 /** 236 * Add a filter query to the existing ones (as a {@link Query} object). 237 * @param query the filter query to add (as a {@link Query} object). 238 * @return The Searcher object itself. The Searcher object itself. 239 */ 240 public Searcher addFilterQuery(Query query) 241 { 242 _filterQueries.add(query); 243 return this; 244 } 245 246 /** 247 * Set the sort clauses. 248 * @param sortClauses the sort clauses. 249 * @return The Searcher object itself. 250 */ 251 public Searcher withSort(Sort... sortClauses) 252 { 253 _sortClauses = new ArrayList<>(sortClauses.length); 254 CollectionUtils.addAll(_sortClauses, sortClauses); 255 return this; 256 } 257 258 /** 259 * Set the sort clauses. 260 * @param sortClauses the sort clauses. 261 * @return The Searcher object itself. 262 */ 263 public Searcher withSort(List<Sort> sortClauses) 264 { 265 _sortClauses = new ArrayList<>(sortClauses); 266 return this; 267 } 268 269 /** 270 * Add a sort clause to the existing ones. 271 * @param sortClause The sort clause to add. 272 * @return The Searcher object itself. 273 */ 274 public Searcher addSort(Sort sortClause) 275 { 276 _sortClauses.add(sortClause); 277 return this; 278 } 279 280 /** 281 * Set the faceted fields. 282 * @param facets the faceted fields. 283 * @return The Searcher object itself. 284 */ 285 public Searcher withFacets(SearchField... facets) 286 { 287 _facets = new ArrayList<>(facets.length); 288 CollectionUtils.addAll(_facets, facets); 289 return this; 290 } 291 292 /** 293 * Set the faceted fields. 294 * @param facets the faceted fields. 295 * @return The Searcher object itself. 296 */ 297 public Searcher withFacets(Collection<SearchField> facets) 298 { 299 _facets = new ArrayList<>(facets); 300 return this; 301 } 302 303 /** 304 * Add a faceted field. 305 * @param facet The faceted field to add. 306 * @return The Searcher object itself. 307 */ 308 public Searcher addFacet(SearchField facet) 309 { 310 _facets.add(facet); 311 return this; 312 } 313 314 /** 315 * Set the facet values. 316 * @param facetValues The facet values. 317 * @return The Searcher object itself. 318 */ 319 public Searcher withFacetValues(Map<String, List<String>> facetValues) 320 { 321 _facetValues = new HashMap<>(facetValues); 322 return this; 323 } 324 325 /** 326 * Set the search offset and limit. 327 * @param start The start index (offset). 328 * @param maxResults The maximum number of results. 329 * @return The Searcher object itself. 330 */ 331 public Searcher withLimits(int start, int maxResults) 332 { 333 this._start = start; 334 this._maxResults = maxResults; 335 return this; 336 } 337 338 /** 339 * Set the search context. 340 * @param searchContext The search context. 341 * @return The Searcher object itself. 342 */ 343 public Searcher withContext(Map<String, Object> searchContext) 344 { 345 _searchContext = new HashMap<>(searchContext); 346 return this; 347 } 348 349 /** 350 * Add a value to the search context. 351 * @param key The context key. 352 * @param value The value. 353 * @return The Searcher object itself. 354 */ 355 public Searcher addContextElement(String key, Object value) 356 { 357 _searchContext.put(key, value); 358 return this; 359 } 360 361 /** 362 * Whether to check rights when searching, false otherwise. 363 * @param checkRights <code>true</code> to check rights, <code>false</code> otherwise. 364 * @return The Searcher object itself. 365 */ 366 public Searcher setCheckRights(boolean checkRights) 367 { 368 _checkRights = checkRights; 369 return this; 370 } 371 372 /** 373 * Check rights when searching, <b>not</b> according to the current user, 374 * but according to the given {@link AllowedUsers visibilty} to compare each 375 * result with. 376 * @param compareTo the {@link AllowedUsers visibilty} to compare each result with. 377 * @return The Searcher object itself. 378 */ 379 public Searcher checkRightsComparingTo(AllowedUsers compareTo) 380 { 381 _checkRights = false; 382 _checkRightsComparingTo = compareTo; 383 return this; 384 } 385 386 /** 387 * Sets the debug on the Solr query 388 * @return The Searcher object itself. 389 */ 390 public Searcher setDebugOn() 391 { 392 _debug = true; 393 return this; 394 } 395 396 /** 397 * Execute the search with the current parameters. 398 * @param <A> The type of search results 399 * @return An iterable on the result ametys objects. 400 * @throws Exception If an error occurs. 401 */ 402 public <A extends AmetysObject> AmetysObjectIterable<A> search() throws Exception 403 { 404 SearchResults<A> searchResults = searchWithFacets(); 405 return searchResults.getObjects(); 406 } 407 408 /** 409 * Execute the search with the current parameters. 410 * @param <A> The type of search results 411 * @return An iterable on the search result objects. 412 * @throws Exception If an error occurs. 413 */ 414 public <A extends AmetysObject> SearchResults<A> searchWithFacets() throws Exception 415 { 416 QueryResponse response = _querySolrServer(); 417 return _buildResults(response, _facets); 418 } 419 420 /** 421 * From the Solr server response, builds the {@link SearchResults} object. 422 * @param <A> The type of search results 423 * @param response The response from the Solr server 424 * @param facets The facet fields to return 425 * @return An iterable on the search result objects. 426 * @throws Exception If an error occurs. 427 */ 428 protected <A extends AmetysObject> SearchResults<A> _buildResults(QueryResponse response, List<SearchField> facets) throws Exception 429 { 430 _handleDebug(response); 431 Map<String, Map<String, Integer>> facetResults = getFacetResults(response, facets); 432 return new SolrSearchResults<>(response, _resolver, facetResults); 433 } 434 435 private void _handleDebug(QueryResponse response) 436 { 437 if (_debug && _logger.isDebugEnabled()) 438 { 439 Map<String, Object> debugMap = response.getDebugMap(); 440 _logger.debug("Debug response: \n{}", debugMap); 441 } 442 } 443 444 private QueryResponse _querySolrServer() throws Exception 445 { 446 _logSearchQueries(); 447 String query = getQuery(); 448 List<String> filterQueries = getFilterQueries(); 449 450 SolrQuery solrQuery = getSolrQuery(query, filterQueries, _start, _maxResults, _searchContext, _checkRights, _checkRightsComparingTo); 451 452 // Set the sort specification and facets in the solr query object. 453 setSort(solrQuery, _sortClauses); 454 setFacets(solrQuery, _facets, _facetValues); 455 456 modifySolrQuery(solrQuery); 457 458 if (_logger.isInfoEnabled()) 459 { 460 _logger.info("Solr query: " + URLDecoder.decode(solrQuery.toString(), "UTF-8")); 461 } 462 463 // Use POST to avoid problems with large requests. 464 return _solrClient.query(_solrClientProvider.getCollectionName(), solrQuery, METHOD.POST); 465 } 466 467 private void _logSearchQueries() 468 { 469 if (!_logger.isDebugEnabled()) 470 { 471 return; 472 } 473 474 if (_queryString == null && _query != null) 475 { 476 _logger.debug("Query before building: \n{}", _query.toString(0)); 477 } 478 if (!_filterQueries.isEmpty()) 479 { 480 _logger.debug("Filter Queries before building: \n{}", _filterQueries 481 .stream() 482 .map(fq -> fq.toString(0)) 483 .collect(Collectors.joining("\n###\n"))); 484 } 485 } 486 487 /** 488 * Get the query string from the parameters. 489 * @return The query string. 490 * @throws QuerySyntaxException If the query is invalid. 491 */ 492 protected String getQuery() throws QuerySyntaxException 493 { 494 String query = "*:*"; 495 if (_queryString != null) 496 { 497 query = _queryString; 498 } 499 else if (_query != null) 500 { 501 query = _query.build(); 502 } 503 return query; 504 } 505 506 /** 507 * Get the filter queries from the parameters. 508 * @return The list of filter queries. 509 * @throws QuerySyntaxException If one of the queries is invalid. 510 */ 511 protected List<String> getFilterQueries() throws QuerySyntaxException 512 { 513 List<String> filterQueries = new ArrayList<>(); 514 if (!_filterQueryStrings.isEmpty()) 515 { 516 filterQueries = _filterQueryStrings; 517 } 518 519 if (!_filterQueries.isEmpty()) 520 { 521 for (Query fq : _filterQueries) 522 { 523 filterQueries.add(fq.build()); 524 } 525 } 526 527 return filterQueries; 528 } 529 530 /** 531 * Get the solr query object. 532 * @param query The solr query string. 533 * @param filterQueries The filter queries (as Strings). 534 * @param start The start index. 535 * @param maxResults The maximum number of results. 536 * @param searchContext The search context. 537 * @param checkRights Whether to check rights when searching or not. 538 * @param allowedUsersToCompare The {@link AllowedUsers} object to compare with for checking rights 539 * @return The solr query object. 540 * @throws Exception If an error occurs. 541 */ 542 protected SolrQuery getSolrQuery(String query, Collection<String> filterQueries, int start, int maxResults, Map<String, Object> searchContext, boolean checkRights, AllowedUsers allowedUsersToCompare) throws Exception 543 { 544 SolrQuery solrQuery = new SolrQuery(); 545 546 String queryString = StringUtils.isNotBlank(query) ? query : "*:*"; 547 548 // Set the query string, pagination spec and fields to be returned. 549 solrQuery.setQuery(queryString); 550 solrQuery.setStart(start); 551 solrQuery.setRows(maxResults); 552 // Return only ID + score fields. 553 solrQuery.setFields("id", "score"); 554 555 // Add filter queries. 556 for (String fq : filterQueries) 557 { 558 solrQuery.addFilterQuery(fq); 559 } 560 561 if (checkRights) 562 { 563 _checkRightsQuery(solrQuery); 564 } 565 else if (allowedUsersToCompare != null) 566 { 567 _checkAllowedUsers(solrQuery, allowedUsersToCompare); 568 } 569 570 if (_debug) 571 { 572 solrQuery.setShowDebugInfo(true); 573 } 574 575 return solrQuery; 576 } 577 578 private void _checkRightsQuery(SolrQuery solrQuery) 579 { 580 StringBuilder aclFilterQuery = new StringBuilder("{!acl "); 581 UserIdentity user = _currentUserProvider.getUser(); 582 if (user == null) 583 { 584 aclFilterQuery.append("anonymous="); 585 } 586 else 587 { 588 aclFilterQuery.append("populationId="); 589 aclFilterQuery.append(user.getPopulationId()); 590 aclFilterQuery.append(" login="); 591 aclFilterQuery.append(user.getLogin()); 592 Set<GroupIdentity> groups = _groupManager.getUserGroups(user); 593 if (!groups.isEmpty()) 594 { 595 String groupsAsStr = groups.stream() 596 .map(GroupIdentity::groupIdentityToString) 597 .collect(Collectors.joining(",")); 598 aclFilterQuery.append(" groups=").append('"'); 599 aclFilterQuery.append(groupsAsStr).append('"'); 600 } 601 } 602 aclFilterQuery.append("}"); 603 // {!acl anonymous=} or {!acl populationId=users login=user1 groups="group1#groupDirectory,group2#groupDirectory"} to check acl 604 solrQuery.addFilterQuery(aclFilterQuery.toString()); 605 } 606 607 private void _checkAllowedUsers(SolrQuery solrQuery, AllowedUsers allowedUsersToCompare) 608 { 609 StringBuilder fq = new StringBuilder("{!aclCompare "); 610 if (allowedUsersToCompare.isAnonymousAllowed()) 611 { 612 fq.append("anonymous=true"); 613 } 614 else 615 { 616 if (allowedUsersToCompare.isAnyConnectedUserAllowed()) 617 { 618 fq.append("anyConnected=true "); 619 } 620 Set<String> allowedUsers = allowedUsersToCompare.getAllowedUsers().stream().map(UserIdentity::userIdentityToString).collect(Collectors.toSet()); 621 Set<String> deniedUsers = allowedUsersToCompare.getDeniedUsers().stream().map(UserIdentity::userIdentityToString).collect(Collectors.toSet()); 622 Set<String> allowedGroups = allowedUsersToCompare.getAllowedGroups().stream().map(GroupIdentity::groupIdentityToString).collect(Collectors.toSet()); 623 Set<String> deniedGroups = allowedUsersToCompare.getDeniedGroups().stream().map(GroupIdentity::groupIdentityToString).collect(Collectors.toSet()); 624 if (!allowedUsers.isEmpty()) 625 { 626 fq.append("allowedUsers=").append(String.join(",", allowedUsers)).append(" "); 627 } 628 if (!deniedUsers.isEmpty()) 629 { 630 fq.append("deniedUsers=").append(String.join(",", deniedUsers)).append(" "); 631 } 632 if (!allowedGroups.isEmpty()) 633 { 634 fq.append("allowedGroups=").append(String.join(",", allowedGroups)).append(" "); 635 } 636 if (!deniedGroups.isEmpty()) 637 { 638 fq.append("deniedGroups=").append(String.join(",", deniedGroups)); 639 } 640 } 641 fq.append("}"); 642 solrQuery.addFilterQuery(fq.toString()); 643 } 644 645 /** 646 * Set the sort definition in the solr query object. 647 * @param solrQuery The solr query object. 648 * @param sortCriteria The sort criteria. 649 */ 650 protected void setSort(SolrQuery solrQuery, List<Sort> sortCriteria) 651 { 652 if (sortCriteria.isEmpty()) 653 { 654 solrQuery.addSort("score", ORDER.desc); 655 } 656 for (Sort sortCriterion : sortCriteria) 657 { 658 solrQuery.addSort(sortCriterion.getField(), sortCriterion.getOrder() == Order.ASC ? ORDER.asc : ORDER.desc); 659 } 660 } 661 662 /** 663 * Set the facet definition in the solr query object and return a mapping from solr field name to criterion ID. 664 * @param solrQuery the solr query object to fill. 665 * @param facets The facet definitions to use. 666 * @param facetValues the facet values. 667 */ 668 protected void setFacets(SolrQuery solrQuery, Collection<SearchField> facets, Map<String, List<String>> facetValues) 669 { 670 if (facets.size() > 0) 671 { 672 // unlimited facet values 673 solrQuery.setFacetLimit(-1); 674 solrQuery.setFacet(true); 675 solrQuery.setFacetMinCount(1); 676 } 677 678 for (SearchField facetField : facets) 679 { 680 String fieldName = facetField.getName(); 681 String solrFieldName = facetField.getFacetField(); 682 683 if (StringUtils.isNotBlank(fieldName)) 684 { 685 if (facetField.isJoined()) 686 { 687 _setJoinedFacet(solrQuery, fieldName, facetField.getJoinedPaths(), solrFieldName, facetField.getFacetFunction(), facetValues); 688 } 689 else 690 { 691 _setNonJoinedFacet(solrQuery, fieldName, solrFieldName, facetValues); 692 } 693 } 694 } 695 } 696 697 private void _setJoinedFacet(SolrQuery solrQuery, String fieldName, List<String> joinedPaths, String solrFieldName, String facetFunction, Map<String, List<String>> facetValues) 698 { 699 List<String> fieldFacetValues = facetValues.get(fieldName); 700 if (fieldFacetValues != null && !fieldFacetValues.isEmpty()) 701 { 702 StringBuilder fq = new StringBuilder(); 703 fq.append("{!tag=").append(fieldName).append("}("); 704 705 int i = 0; 706 fq.append("{!ametys join=").append(StringUtils.join(joinedPaths, "->")).append(" q=\""); 707 for (String facetValue : fieldFacetValues) 708 { 709 if (i > 0) 710 { 711 fq.append(" OR "); 712 } 713 fq.append(solrFieldName).append(":\\\"").append(facetValue).append("\\\""); 714 i++; 715 } 716 fq.append("\"})"); 717 solrQuery.addFilterQuery(fq.toString()); 718 } 719 720 solrQuery.add("facet.function", "{!ex=" + fieldName + " key=" + fieldName + "}" + facetFunction); 721 } 722 723 private void _setNonJoinedFacet(SolrQuery solrQuery, String fieldName, String solrFieldName, Map<String, List<String>> facetValues) 724 { 725 List<String> fieldFacetValues = facetValues.get(fieldName); 726 if (fieldFacetValues != null && !fieldFacetValues.isEmpty()) 727 { 728 StringBuilder fq = new StringBuilder(); 729 fq.append("{!tag=").append(fieldName).append("}("); 730 731 int i = 0; 732 for (String facetValue : fieldFacetValues) 733 { 734 if (i > 0) 735 { 736 fq.append(" OR "); 737 } 738 // TODO escape query chars? 739 fq.append(solrFieldName).append(":\"").append(facetValue).append('"'); 740 i++; 741 } 742 fq.append(')'); 743 solrQuery.addFilterQuery(fq.toString()); 744 } 745 746 String facetFieldDef = "{!ex=" + fieldName + " key=" + fieldName + "}" + solrFieldName; 747 solrQuery.addFacetField(facetFieldDef); 748 } 749 750 /** 751 * Retrieve the facet results from the solr response. 752 * @param response the solr response. 753 * @param facets The facet fields to return. 754 * @return the facet results. 755 */ 756 protected Map<String, Map<String, Integer>> getFacetResults(QueryResponse response, Collection<SearchField> facets) 757 { 758 Map<String, Map<String, Integer>> facetResults = new LinkedHashMap<>(); 759 760 for (SearchField facetField : facets) 761 { 762 String fieldName = facetField.getName(); 763 FacetField solrFacetField = response.getFacetField(fieldName); 764 765 List<Count> values = solrFacetField.getValues(); 766 767 Map<String, Integer> solrFacetValues = new HashMap<>(); 768 facetResults.put(fieldName, solrFacetValues); 769 770 for (Count count : values) 771 { 772 solrFacetValues.put(count.getName(), (int) count.getCount()); 773 } 774 } 775 776 return facetResults; 777 } 778 779 /** 780 * Template method to do additional operations on the Solr query before passing it to the Solr client 781 * @param query the Solr query 782 */ 783 protected void modifySolrQuery(SolrQuery query) 784 { 785 // do nothing by default 786 } 787 } 788 789}