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