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 */ 016 017package org.ametys.web.frontoffice; 018 019import java.io.IOException; 020import java.time.LocalDate; 021import java.time.format.DateTimeFormatter; 022import java.time.format.DateTimeParseException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Date; 028import java.util.Enumeration; 029import java.util.HashMap; 030import java.util.HashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034 035import org.apache.cocoon.environment.ObjectModelHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.cocoon.xml.AttributesImpl; 038import org.apache.cocoon.xml.XMLUtils; 039import org.apache.commons.lang3.ArrayUtils; 040import org.apache.commons.lang3.StringUtils; 041import org.apache.solr.client.solrj.util.ClientUtils; 042import org.xml.sax.SAXException; 043 044import org.ametys.cms.content.indexing.solr.SolrFieldNames; 045import org.ametys.cms.contenttype.ContentType; 046import org.ametys.cms.contenttype.MetadataDefinition; 047import org.ametys.cms.repository.RequestAttributeWorkspaceSelector; 048import org.ametys.cms.search.SearchResult; 049import org.ametys.cms.search.SearchResults; 050import org.ametys.cms.search.SearchResultsIterable; 051import org.ametys.cms.search.SearchResultsIterator; 052import org.ametys.cms.search.Sort; 053import org.ametys.cms.search.Sort.Order; 054import org.ametys.cms.search.query.AndQuery; 055import org.ametys.cms.search.query.ContentTypeQuery; 056import org.ametys.cms.search.query.DateQuery; 057import org.ametys.cms.search.query.DocumentTypeQuery; 058import org.ametys.cms.search.query.FullTextQuery; 059import org.ametys.cms.search.query.NotQuery; 060import org.ametys.cms.search.query.OrQuery; 061import org.ametys.cms.search.query.Query; 062import org.ametys.cms.search.query.Query.Operator; 063import org.ametys.cms.search.query.QuerySyntaxException; 064import org.ametys.cms.search.query.StringQuery; 065import org.ametys.cms.search.query.TagQuery; 066import org.ametys.cms.search.solr.SearcherFactory.Searcher; 067import org.ametys.cms.tag.Tag; 068import org.ametys.cms.tag.TagProvider; 069import org.ametys.core.util.DateUtils; 070import org.ametys.plugins.explorer.resources.Resource; 071import org.ametys.plugins.repository.AmetysObject; 072import org.ametys.runtime.i18n.I18nizableText; 073import org.ametys.web.repository.page.Page; 074import org.ametys.web.repository.page.ZoneItem; 075import org.ametys.web.repository.site.Site; 076import org.ametys.web.search.query.PageContentQuery; 077import org.ametys.web.search.query.PageQuery; 078import org.ametys.web.search.query.SiteQuery; 079import org.ametys.web.search.query.SitemapQuery; 080 081/** 082 * Generates the results of a search performed on front office 083 */ 084public class SearchGenerator extends AbstractSearchGenerator 085{ 086 /** Constants for content type's choice : filter */ 087 protected static final String CONTENT_TYPE_CHOICE_FILTER = "filter"; 088 089 /** Constants for content type's choice : list */ 090 protected static final String CONTENT_TYPE_CHOICE_LIST = "list"; 091 092 /** Constants for content type's choice : checkbox then filter */ 093 protected static final String CONTENT_TYPE_CHOICE_CHECKBOX = "checkbox"; 094 095 /** Constants for content type's choice : checkbox then filter */ 096 protected static final String CONTENT_TYPE_CHOICE_CHECKBOX_AND_FILTER = "checkbox-filter"; 097 098 /** Constants for content type's choice : none */ 099 protected static final String CONTENT_TYPE_CHOICE_NONE = "none"; 100 101 @Override 102 protected SearchResults<AmetysObject> search(Request request, Collection<String> siteNames, String language, int pageIndex, int start, int maxResults) throws Exception 103 { 104 String searchCTypeType = parameters.getParameter("search-by-content-types-choice", CONTENT_TYPE_CHOICE_NONE); 105 106 try 107 { 108 // TODO Use facets instead of one query by content type! 109 if (CONTENT_TYPE_CHOICE_FILTER.equals(searchCTypeType) || CONTENT_TYPE_CHOICE_CHECKBOX_AND_FILTER.equals(searchCTypeType)) 110 { 111 // Retrieve current workspace 112 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 113 SearchResults<AmetysObject> currentResults = null; 114 115 try 116 { 117 XMLUtils.startElement(contentHandler, "content-types"); 118 Collection<String> cTypes = getContentTypes(request); 119 120 List<Sort> sorts = new ArrayList<>(); 121 Sort sort = getSortField(request); 122 sorts.addAll(getPrimarySortFields(request)); 123 sorts.add(sort); 124 125 // Get first sort field 126 XMLUtils.createElement(contentHandler, sort == null || sort.getField() == null ? "sort-by-score" : "sort-by-" + sort.getField()); 127 128 // TODO Use facets instead of making one search by content 129 // type. 130 String currentCType = getContentTypeFilterValue(request); 131 int count = 0; 132 for (String cType : cTypes) 133 { 134 Query queryObject = getQuery(request, siteNames, language); 135 136 Collection<Query> filterQueries = getFixedCTypeFilterQueries(request, cType, siteNames, language); 137 138 Collection<String> documentTypes = getDocumentTypes(request); 139 Query documentTypesQuery = getDocumentTypesQuery(documentTypes); 140 141 Searcher searcher = _searcherFactory.create().withQuery(queryObject).withFilterQueries(filterQueries).addFilterQuery(documentTypesQuery).withLimits(0, 142 Integer.MAX_VALUE).withSort(sorts).setCheckRights(true); 143 144 _additionalSearchProcessing(searcher); 145 146 SearchResults<AmetysObject> results = searcher.searchWithFacets(); 147 148 _searcherFactory.create().withQuery(queryObject).withFilterQueries(filterQueries).addFilterQuery(documentTypesQuery).withLimits(0, 149 Integer.MAX_VALUE).withSort(getPrimarySortFields(request)).addSort(sort).setCheckRights(true); 150 151 if (results.getTotalCount() > 0) 152 { 153 boolean current = cType.equals(currentCType) || currentCType == null && count == 0; 154 count++; 155 AttributesImpl attr = new AttributesImpl(); 156 if (current) 157 { 158 attr.addCDATAAttribute("current", "true"); 159 } 160 161 XMLUtils.startElement(contentHandler, cType, attr); 162 163 if (cType.equals("resource")) 164 { 165 new I18nizableText("plugin.web", "PLUGINS_WEB_SERVICE_FRONT_SEARCH_ON_DOCUMENTS").toSAX(contentHandler, "label"); 166 } 167 else 168 { 169 ContentType contentType = _cTypeExtPt.getExtension(cType); 170 if (contentType != null) 171 { 172 contentType.getLabel().toSAX(contentHandler, "label"); 173 } 174 } 175 176 // SAX results 177 AttributesImpl atts = new AttributesImpl(); 178 atts.addCDATAAttribute("total", String.valueOf(results.getTotalCount())); 179 atts.addCDATAAttribute("maxScore", String.valueOf(results.getMaxScore())); 180 181 if (current) 182 { 183 XMLUtils.startElement(contentHandler, "hits", atts); 184 saxHits(results, start, maxResults); 185 XMLUtils.endElement(contentHandler, "hits"); 186 187 // SAX pagination 188 saxPagination(results.getTotalCount(), start, maxResults); 189 190 currentResults = results; 191 } 192 else 193 { 194 XMLUtils.createElement(contentHandler, "hits", atts); 195 } 196 197 XMLUtils.endElement(contentHandler, cType); 198 } 199 } 200 XMLUtils.endElement(contentHandler, "content-types"); 201 202 return currentResults; 203 } 204 finally 205 { 206 // Restore context 207 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 208 } 209 } 210 else 211 { 212 return super.search(request, siteNames, language, pageIndex, start, maxResults); 213 } 214 } 215 catch (QuerySyntaxException e) 216 { 217 throw new IOException("Query syntax error while searching.", e); 218 } 219 } 220 221 @Override 222 protected Query getQuery(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException 223 { 224 List<Query> queries = new ArrayList<>(); 225 226 addMetadataQuery(queries, language, request); 227 _addTextFieldQuery(queries, language, request); 228 _addAllWordsTextFieldQuery(queries, language, request); 229 _addExactWordingTextFieldQuery(queries, language, request); 230 _addNoWordsTextFieldQuery(queries, language, request); 231 232 return new AndQuery(queries); 233 } 234 235 @Override 236 protected Collection<Query> getFilterQueries(Request request, Collection<String> siteNames, String language) throws IllegalArgumentException 237 { 238 List<Query> queries = new ArrayList<>(); 239 240 queries.add(new SiteQuery(siteNames)); 241 queries.add(new SitemapQuery(language)); 242 243 _addContentTypeQuery(queries, request); 244 _addTagsQuery(queries, request); 245 _addPagesQuery(queries, request); 246 _addDateQuery(queries, request); 247 248 return queries; 249 } 250 251 /** 252 * Get the filter queries for a single fixed content type. 253 * 254 * @param request The request. 255 * @param cType The fixed content type. 256 * @param siteNames the site names. 257 * @param language The language. 258 * @return The filter queries. 259 */ 260 protected Collection<Query> getFixedCTypeFilterQueries(Request request, String cType, Collection<String> siteNames, String language) 261 { 262 List<Query> queries = new ArrayList<>(); 263 264 queries.add(new SiteQuery(siteNames)); 265 queries.add(new SitemapQuery(language)); 266 267 if ("resource".equals(cType)) 268 { 269 queries.add(new DocumentTypeQuery(TYPE_PAGE_RESOURCE)); 270 } 271 else 272 { 273 // Fixed content type query. 274 queries.add(new PageContentQuery(new ContentTypeQuery(cType))); 275 } 276 _addTagsQuery(queries, request); 277 _addPagesQuery(queries, request); 278 _addDateQuery(queries, request); 279 280 return queries; 281 } 282 283 @Override 284 protected Collection<String> getDocumentTypes(Request request) 285 { 286 Collection<String> cTypes = getContentTypes(request); 287 288 if (cTypes.size() == 1 && "resource".equals(cTypes.iterator().next())) 289 { 290 return Collections.singletonList(TYPE_PAGE_RESOURCE); 291 } 292 // An empty collections means "all". 293 else if (cTypes.isEmpty() || cTypes.contains("resource")) 294 { 295 return Arrays.asList(TYPE_PAGE, TYPE_PAGE_RESOURCE); 296 } 297 else 298 { 299 return Collections.singletonList(TYPE_PAGE); 300 } 301 } 302 303 @Override 304 protected Collection<String> getFields() 305 { 306 return Collections.emptyList(); 307 } 308 309 @Override 310 protected Map<String, Criterion> getCriteria(Request request, String siteName, String lang) 311 { 312 return Collections.emptyMap(); 313 } 314 315 @Override 316 protected void saxHits(SearchResults<AmetysObject> results, int start, int maxResults) throws SAXException, IOException 317 { 318 SearchResultsIterable<SearchResult<AmetysObject>> resultsIt = results.getResults(); 319 long limit = Math.min(start + maxResults, resultsIt.getSize()); 320 float maxScore = results.getMaxScore(); 321 322 SearchResultsIterator<SearchResult<AmetysObject>> it = resultsIt.iterator(); 323 it.skip(start); 324 for (int i = start; i < limit; i++) 325 { 326 if (it.hasNext()) // this should return true except if there is a inconsistency between repository and Solr index 327 { 328 SearchResult<AmetysObject> searchResult = it.next(); 329 float score = searchResult.getScore(); 330 AmetysObject ametysObject = searchResult.getObject(); 331 if (ametysObject instanceof Page) 332 { 333 saxPageHit(score, maxScore, (Page) ametysObject); 334 } 335 else if (ametysObject instanceof Resource) 336 { 337 saxResourceHit(score, maxScore, (Resource) ametysObject); 338 } 339 } 340 341 } 342 } 343 344 @Override 345 protected Sort getSortField(Request request) 346 { 347 if (request.getParameter("sort-by-title-for-sorting") != null) 348 { 349 return new Sort(TITLE_SORT, Order.ASC); 350 } 351 else if (request.getParameter("sort-by-last-validation") != null) 352 { 353 return new Sort(LAST_VALIDATION, Order.DESC); 354 } 355 else 356 { 357 // Generic sort field (with hardcorded descending order) 358 Enumeration paramNames = request.getParameterNames(); 359 while (paramNames.hasMoreElements()) 360 { 361 String param = (String) paramNames.nextElement(); 362 if (param.startsWith("sort-by-")) 363 { 364 String fieldName = StringUtils.removeStart(param, "sort-by-"); 365 return new Sort(fieldName, Order.DESC); 366 } 367 } 368 } 369 370 return new Sort("score", Order.DESC); 371 } 372 373 @Override 374 protected List<Sort> getPrimarySortFields(Request request) 375 { 376 return new ArrayList<>(); 377 } 378 379 @Override 380 protected void saxFormFields(Request request, SearchResults<AmetysObject> searchResults, String siteName, String lang) throws SAXException 381 { 382 XMLUtils.createElement(contentHandler, "textfield"); 383 384 boolean advancedSearch = parameters.getParameterAsBoolean("advanced-search", true); 385 if (advancedSearch) 386 { 387 XMLUtils.createElement(contentHandler, "all-words"); 388 XMLUtils.createElement(contentHandler, "exact-wording"); 389 XMLUtils.createElement(contentHandler, "no-words"); 390 } 391 392 String searchCTypeType = parameters.getParameter("search-by-content-types-choice", CONTENT_TYPE_CHOICE_NONE); 393 XMLUtils.createElement(contentHandler, "content-types-choice", searchCTypeType); 394 395 _saxContentTypeCriteria(); 396 _saxMetadataCriteria(request); 397 _saxTagsCriteria(siteName); 398 _saxSitemapCriteria(); 399 400 boolean multisite = parameters.getParameterAsBoolean("search-multisite", false); 401 if (multisite) 402 { 403 XMLUtils.createElement(contentHandler, "multisite"); 404 405 XMLUtils.startElement(contentHandler, "sites"); 406 Collection<String> allSites = _siteManager.getSiteNames(); 407 for (String name : allSites) 408 { 409 Site site = _siteManager.getSite(name); 410 AttributesImpl attr = new AttributesImpl(); 411 attr.addCDATAAttribute("name", name); 412 if (name.equals(siteName)) 413 { 414 attr.addCDATAAttribute("current", "true"); 415 } 416 XMLUtils.createElement(contentHandler, "site", attr, StringUtils.defaultString(site.getTitle())); 417 } 418 XMLUtils.endElement(contentHandler, "sites"); 419 } 420 421 if (StringUtils.isNotBlank(parameters.getParameter("startDate", ""))) 422 { 423 XMLUtils.createElement(contentHandler, "dates", "true"); 424 } 425 } 426 427 private void _saxMetadataCriteria(Request request) throws SAXException 428 { 429 ZoneItem zoneItem = (ZoneItem) request.getAttribute(org.ametys.web.repository.page.ZoneItem.class.getName()); 430 if (zoneItem.getServiceParameters().hasMetadata("search-by-metadata")) 431 { 432 String[] metadataPaths = zoneItem.getServiceParameters().getStringArray("search-by-metadata"); 433 if (metadataPaths.length > 0) 434 { 435 Collection<String> cTypes = new ArrayList<>(getContentTypes(request)); 436 437 for (String metadataPath : metadataPaths) 438 { 439 MetadataDefinition metadataDef = _contentTypesHelper.getMetadataDefinitionByMetadataValuePath(metadataPath, cTypes.toArray(new String[cTypes.size()]), 440 new String[0]); 441 AttributesImpl attrs = new AttributesImpl(); 442 attrs.addCDATAAttribute("name", metadataPath); 443 XMLUtils.startElement(contentHandler, "metadata", attrs); 444 445 if (metadataDef != null) 446 { 447 metadataDef.getLabel().toSAX(contentHandler); 448 } 449 else 450 { 451 XMLUtils.data(contentHandler, metadataPath); 452 } 453 XMLUtils.endElement(contentHandler, "metadata"); 454 455 saxMetadataDef(metadataDef, metadataPath); 456 } 457 } 458 } 459 } 460 461 /** 462 * Sax metadata set information for metadata 463 * 464 * @param metadataDef The metadata definition. 465 * @param metadataPath The metadata path 466 * @throws SAXException If an error occurred while saxing 467 */ 468 protected void saxMetadataDef(MetadataDefinition metadataDef, String metadataPath) throws SAXException 469 { 470 AttributesImpl attrs = new AttributesImpl(); 471 attrs.addCDATAAttribute("name", metadataPath); 472 473 XMLUtils.startElement(contentHandler, "metadataDef", attrs); 474 if (metadataDef != null) 475 { 476 metadataDef.getLabel().toSAX(contentHandler, "label"); 477 } 478 else 479 { 480 XMLUtils.startElement(contentHandler, "label"); 481 XMLUtils.data(contentHandler, metadataPath); 482 XMLUtils.endElement(contentHandler, "label"); 483 } 484 485 saxEnumeratorValueForMetadata(metadataDef, metadataPath); 486 XMLUtils.endElement(contentHandler, "metadataDef"); 487 } 488 489 /** 490 * Sax enumeration value for enum metadata 491 * 492 * @param metadataDef The metadata definition. 493 * @param metadataPath The metadata path 494 * @throws SAXException If an error occurred while saxing 495 */ 496 protected void saxEnumeratorValueForMetadata(MetadataDefinition metadataDef, String metadataPath) throws SAXException 497 { 498 if (metadataDef != null && metadataDef.getEnumerator() != null) 499 { 500 XMLUtils.startElement(contentHandler, "enumeration"); 501 try 502 { 503 Map<Object, I18nizableText> entries = metadataDef.getEnumerator().getEntries(); 504 for (Object key : entries.keySet()) 505 { 506 AttributesImpl attrItem = new AttributesImpl(); 507 attrItem.addCDATAAttribute("value", (String) key); 508 XMLUtils.startElement(contentHandler, "item", attrItem); 509 entries.get(key).toSAX(contentHandler, "label"); 510 XMLUtils.endElement(contentHandler, "item"); 511 } 512 } 513 catch (Exception e) 514 { 515 getLogger().error("An error occurred getting enumerator items for metadata : " + metadataPath, e); 516 } 517 XMLUtils.endElement(contentHandler, "enumeration"); 518 } 519 } 520 521 @Override 522 protected void saxFormValues(Request request, SearchResults<AmetysObject> searchResults, int start, int offset) throws SAXException 523 { 524 _saxTextField(request); 525 _saxMetadataValues(request); 526 _saxAllWords(request); 527 _saxExactWording(request); 528 _saxNoWords(request); 529 _saxContentType(request); 530 _saxTags(request); 531 _saxPages(request); 532 _saxMultisite(request); 533 _saxDates(request); 534 } 535 536 private void _saxDates(Request request) throws SAXException 537 { 538 String startDate = request.getParameter("startDate"); 539 String endDate = request.getParameter("endDate"); 540 541 if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate) && startDate.compareTo(endDate) > 0) 542 { 543 String tmp = startDate; 544 startDate = endDate; 545 endDate = tmp; 546 } 547 548 if (StringUtils.isNotBlank(startDate)) 549 { 550 XMLUtils.createElement(contentHandler, "startDate", startDate); 551 } 552 if (StringUtils.isNotBlank(endDate)) 553 { 554 XMLUtils.createElement(contentHandler, "endDate", endDate); 555 } 556 } 557 558 private void _saxTextField(Request request) throws SAXException 559 { 560 String textfield = request.getParameter("textfield"); 561 XMLUtils.createElement(contentHandler, "textfield", textfield != null ? textfield : ""); 562 } 563 564 private void _saxMetadataValues(Request request) throws SAXException 565 { 566 ZoneItem zoneItem = (ZoneItem) request.getAttribute(org.ametys.web.repository.page.ZoneItem.class.getName()); 567 if (zoneItem.getServiceParameters().hasMetadata("search-by-metadata")) 568 { 569 String[] metadataPaths = zoneItem.getServiceParameters().getStringArray("search-by-metadata"); 570 if (metadataPaths.length > 0) 571 { 572 XMLUtils.startElement(contentHandler, "metadata"); 573 for (String metadataPath : metadataPaths) 574 { 575 String textfield = request.getParameter("metadata-" + metadataPath.replaceAll("/", ".")); 576 577 AttributesImpl attrs = new AttributesImpl(); 578 attrs.addCDATAAttribute("name", metadataPath); 579 XMLUtils.createElement(contentHandler, "metadata", attrs, textfield != null ? textfield : ""); 580 } 581 XMLUtils.endElement(contentHandler, "metadata"); 582 } 583 } 584 } 585 586 private void _saxAllWords(Request request) throws SAXException 587 { 588 String textfield = request.getParameter("all-words"); 589 XMLUtils.createElement(contentHandler, "all-words", textfield != null ? textfield : ""); 590 } 591 592 private void _saxExactWording(Request request) throws SAXException 593 { 594 String textfield = request.getParameter("exact-wording"); 595 XMLUtils.createElement(contentHandler, "exact-wording", textfield != null ? textfield : ""); 596 } 597 598 private void _saxNoWords(Request request) throws SAXException 599 { 600 String textfield = request.getParameter("no-words"); 601 XMLUtils.createElement(contentHandler, "no-words", textfield != null ? textfield : ""); 602 } 603 604 private void _saxContentType(Request request) throws SAXException 605 { 606 String[] cTypes = request.getParameterValues("content-types"); 607 if (cTypes != null && cTypes.length > 0 && !(cTypes.length == 1 && cTypes[0].equals(""))) 608 { 609 for (String cType : cTypes) 610 { 611 XMLUtils.createElement(contentHandler, "content-type", cType); 612 } 613 } 614 } 615 616 private void _saxTags(Request request) throws SAXException 617 { 618 String size = request.getParameter("tags-size"); 619 if (!StringUtils.isEmpty(size)) 620 { 621 int nbCat = Integer.parseInt(size); 622 for (int i = 1; i < nbCat + 1; i++) 623 { 624 String[] tags = request.getParameterValues("tags-" + i); 625 if (tags != null && tags.length > 0 && !(tags.length == 1 && tags[0].equals(""))) 626 { 627 if (tags.length == 1) 628 { 629 tags = tags[0].split(","); 630 } 631 632 for (String tag : tags) 633 { 634 XMLUtils.createElement(contentHandler, "tag", tag); 635 } 636 637 } 638 } 639 } 640 641 String[] tags = request.getParameterValues("tags"); 642 if (tags != null && tags.length > 0 && !(tags.length == 1 && tags[0].equals(""))) 643 { 644 for (String tag : tags) 645 { 646 XMLUtils.createElement(contentHandler, "tag", tag); 647 } 648 } 649 } 650 651 private void _saxPages(Request request) throws SAXException 652 { 653 String[] pages = request.getParameterValues("pages"); 654 if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals(""))) 655 { 656 for (String id : pages) 657 { 658 XMLUtils.createElement(contentHandler, "page", id); 659 } 660 } 661 } 662 663 private void _saxMultisite(Request request) throws SAXException 664 { 665 boolean multisite = request.getParameter("multisite") != null; 666 if (multisite) 667 { 668 XMLUtils.createElement(contentHandler, "multisite"); 669 670 String[] sites = request.getParameterValues("sites"); 671 if (sites != null && sites.length > 0 && !(sites.length == 1 && sites[0].equals(""))) 672 { 673 for (String site : sites) 674 { 675 XMLUtils.createElement(contentHandler, "site", site); 676 } 677 } 678 679 } 680 } 681 682 /** 683 * Get the content type's 684 * 685 * @param request The request 686 * @return the content type's 687 */ 688 protected Collection<String> getContentTypes(Request request) 689 { 690 String[] cTypes = request.getParameterValues("content-types"); 691 692 if (cTypes != null && cTypes.length > 0 && !(cTypes.length == 1 && cTypes[0].equals(""))) 693 { 694 if (cTypes.length == 1) 695 { 696 cTypes = cTypes[0].split(","); 697 } 698 699 return Arrays.asList(cTypes); 700 } 701 return _getAvailableContentTypes(); 702 } 703 704 private Collection<String> _getAvailableContentTypes() 705 { 706 @SuppressWarnings("unchecked") 707 Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 708 if (parentContext == null) 709 { 710 parentContext = Collections.emptyMap(); 711 } 712 713 String[] serviceCTypes = (String[]) parentContext.get("search-by-content-types"); 714 715 String searchCTypeType = parameters.getParameter("search-by-content-types-choice", CONTENT_TYPE_CHOICE_NONE); 716 if (ArrayUtils.isNotEmpty(serviceCTypes) && StringUtils.isNotBlank(serviceCTypes[0])) 717 { 718 return Arrays.asList(serviceCTypes); 719 } 720 else if (!searchCTypeType.equals(CONTENT_TYPE_CHOICE_NONE)) 721 { 722 return _getAllContentTypes(); 723 } 724 725 return Collections.emptyList(); 726 } 727 728 private Set<String> _getAllContentTypes() 729 { 730 Set<String> allCTypes = new HashSet<>(_cTypeExtPt.getExtensionsIds()); 731 allCTypes.add("resource"); 732 return allCTypes; 733 } 734 735 private void _saxContentTypeCriteria() throws SAXException 736 { 737 Collection<String> cTypes = _getAvailableContentTypes(); 738 739 XMLUtils.startElement(contentHandler, "content-types"); 740 for (String cTypeId : cTypes) 741 { 742 if ("resource".equals(cTypeId)) 743 { 744 AttributesImpl attr = new AttributesImpl(); 745 attr.addCDATAAttribute("id", cTypeId); 746 XMLUtils.startElement(contentHandler, "type", attr); 747 new I18nizableText("plugin.web", "PLUGINS_WEB_SERVICE_FRONT_SEARCH_ON_DOCUMENTS").toSAX(contentHandler); 748 XMLUtils.endElement(contentHandler, "type"); 749 } 750 else if (StringUtils.isNotEmpty(cTypeId)) 751 { 752 ContentType cType = _cTypeExtPt.getExtension(cTypeId); 753 754 AttributesImpl attr = new AttributesImpl(); 755 attr.addCDATAAttribute("id", cTypeId); 756 XMLUtils.startElement(contentHandler, "type", attr); 757 cType.getLabel().toSAX(contentHandler); 758 XMLUtils.endElement(contentHandler, "type"); 759 } 760 761 } 762 XMLUtils.endElement(contentHandler, "content-types"); 763 } 764 765 private void _saxTagsCriteria(String siteName) throws SAXException 766 { 767 @SuppressWarnings("unchecked") 768 Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 769 if (parentContext == null) 770 { 771 parentContext = Collections.emptyMap(); 772 } 773 774 Map<String, Object> contextParameters = new HashMap<>(); 775 contextParameters.put("siteName", siteName); 776 777 String[] tagArray = (String[]) parentContext.get("search-by-tags"); 778 if (ArrayUtils.isNotEmpty(tagArray)) 779 { 780 XMLUtils.startElement(contentHandler, "tags"); 781 for (String tagName : tagArray) 782 { 783 if (StringUtils.isNotEmpty(tagName)) 784 { 785 boolean found = false; 786 Set<String> extensionsIds = _tagExtPt.getExtensionsIds(); 787 788 for (String id : extensionsIds) 789 { 790 if (found) 791 { 792 continue; 793 } 794 795 I18nizableText label = null; 796 Map<String, Tag> tags = null; 797 798 TagProvider tagProvider = _tagExtPt.getExtension(id); 799 if (tagName.startsWith("provider_") && id.equals(tagName.substring("provider_".length()))) 800 { 801 found = true; 802 803 label = tagProvider.getLabel(); 804 805 tags = tagProvider.getTags(contextParameters); 806 } 807 else if (tagProvider.hasTag(tagName, contextParameters)) 808 { 809 found = true; 810 811 Tag tag = tagProvider.getTag(tagName, contextParameters); 812 label = tag.getTitle(); 813 tags = tag.getTags(); 814 } 815 816 if (found) 817 { 818 AttributesImpl attr = new AttributesImpl(); 819 attr.addCDATAAttribute("id", tagName); 820 XMLUtils.startElement(contentHandler, "tag", attr); 821 822 if (label != null) 823 { 824 label.toSAX(contentHandler, "title"); 825 } 826 827 if (tags != null) 828 { 829 for (Tag child : tags.values()) 830 { 831 if (child.getTarget().getName().equals("CONTENT")) 832 { 833 _saxTag(child, true); 834 } 835 } 836 } 837 838 XMLUtils.endElement(contentHandler, "tag"); 839 } 840 } 841 } 842 } 843 XMLUtils.endElement(contentHandler, "tags"); 844 } 845 } 846 847 private void _saxTag(Tag tag, boolean recursive) throws SAXException 848 { 849 AttributesImpl attr = new AttributesImpl(); 850 attr.addCDATAAttribute("id", tag.getName()); 851 XMLUtils.startElement(contentHandler, "tag", attr); 852 tag.getTitle().toSAX(contentHandler, "title"); 853 854 if (recursive) 855 { 856 for (Tag child : tag.getTags().values()) 857 { 858 _saxTag(child, true); 859 } 860 } 861 862 XMLUtils.endElement(contentHandler, "tag"); 863 } 864 865 private void _saxSitemapCriteria() throws SAXException 866 { 867 @SuppressWarnings("unchecked") 868 Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 869 if (parentContext == null) 870 { 871 parentContext = Collections.emptyMap(); 872 } 873 874 String[] pages = (String[]) parentContext.get("search-by-pages"); 875 if (ArrayUtils.isNotEmpty(pages)) 876 { 877 XMLUtils.startElement(contentHandler, "pages"); 878 879 for (String pageID : pages) 880 { 881 if (StringUtils.isNotEmpty(pageID)) 882 { 883 Page page = _resolver.resolveById(pageID); 884 AttributesImpl attr = new AttributesImpl(); 885 attr.addCDATAAttribute("id", pageID); 886 attr.addCDATAAttribute("path", page.getSitemap().getName() + "/" + page.getPathInSitemap()); 887 attr.addCDATAAttribute("title", page.getTitle()); 888 attr.addCDATAAttribute("long-title", page.getLongTitle()); 889 XMLUtils.createElement(contentHandler, "page", attr); 890 } 891 } 892 893 XMLUtils.endElement(contentHandler, "pages"); 894 } 895 } 896 897 /** 898 * Get the content type's filter value 899 * 900 * @param request The request 901 * @return the content type's filter value 902 */ 903 protected String getContentTypeFilterValue(Request request) 904 { 905 Enumeration<String> paramNames = request.getParameterNames(); 906 while (paramNames.hasMoreElements()) 907 { 908 String paramName = paramNames.nextElement(); 909 if (paramName.startsWith("ctype-filter-")) 910 { 911 return paramName.substring("ctype-filter-".length()); 912 } 913 } 914 915 if (request.getParameter("current-ctype-filter") != null) 916 { 917 return request.getParameter("current-ctype-filter"); 918 } 919 920 return null; 921 } 922 923 // one or more of the words 924 private void _addTextFieldQuery(Collection<Query> queries, String language, Request request) throws IllegalArgumentException 925 { 926 String text = request.getParameter("textfield"); 927 928 if (StringUtils.isNotBlank(text)) 929 { 930 String trimText = text.trim(); 931 String escapedText = _escapeQueryCharsButNotQuotes(trimText); 932 933 Query query = new FullTextQuery(escapedText, language, Operator.SEARCH); 934 Query contentQuery = new PageContentQuery(query); 935 936 queries.add(new OrQuery(query, contentQuery)); 937 } 938 } 939 940 /* 941 * Do not escape double quotes for exact searching 942 */ 943 private String _escapeQueryCharsButNotQuotes(String text) 944 { 945 StringBuilder sb = new StringBuilder(); 946 String[] parts = StringUtils.splitPreserveAllTokens(text, '"'); 947 for (int i = 0; i < parts.length; i++) 948 { 949 if (i != 0) 950 { 951 sb.append("\""); 952 } 953 String part = parts[i]; 954 sb.append(ClientUtils.escapeQueryChars(part)); 955 } 956 957 return sb.toString(); 958 } 959 960 /** 961 * Add query for each metadata 962 * 963 * @param queries The list of query 964 * @param language The lang 965 * @param request The request 966 * @throws IllegalArgumentException If an error occurred 967 */ 968 protected void addMetadataQuery(Collection<Query> queries, String language, Request request) throws IllegalArgumentException 969 { 970 ZoneItem zoneItem = (ZoneItem) request.getAttribute(org.ametys.web.repository.page.ZoneItem.class.getName()); 971 if (zoneItem.getServiceParameters().hasMetadata("search-by-metadata")) 972 { 973 String[] metadataPaths = zoneItem.getServiceParameters().getStringArray("search-by-metadata"); 974 for (String metadataPath : metadataPaths) 975 { 976 String value = request.getParameter("metadata-" + metadataPath.replaceAll("/", ".")); 977 978 if (StringUtils.isNotBlank(value)) 979 { 980 Collection<String> contentTypes = getContentTypes(request); 981 982 MetadataDefinition metadataDefinition = contentTypes.isEmpty() ? null 983 : _contentTypesHelper.getMetadataDefinition(metadataPath, (String[]) contentTypes.toArray(), null); 984 if (metadataDefinition == null || metadataDefinition.getEnumerator() == null) 985 { 986 String escapedText = ClientUtils.escapeQueryChars(value).toLowerCase(); 987 if (!escapedText.startsWith("*") && !escapedText.endsWith("*")) 988 { 989 escapedText = "*" + escapedText + "*"; 990 } 991 Query query = new StringQuery(metadataPath, Operator.SEARCH, escapedText, language); 992 Query contentQuery = new PageContentQuery(query); 993 994 queries.add(new OrQuery(query, contentQuery)); 995 } 996 else 997 { 998 Query query = new StringQuery(metadataPath, Operator.EQ, value, language); 999 Query contentQuery = new PageContentQuery(query); 1000 1001 queries.add(new OrQuery(query, contentQuery)); 1002 } 1003 } 1004 } 1005 } 1006 } 1007 1008 // all the words 1009 private void _addAllWordsTextFieldQuery(Collection<Query> queries, String language, Request request) throws IllegalArgumentException 1010 { 1011 // TODO All words? OrQuery of AndQuery instead? 1012 String words = request.getParameter("all-words"); 1013 1014 if (StringUtils.isNotBlank(words)) 1015 { 1016 String escapedWords = ClientUtils.escapeQueryChars(words); 1017 1018 StringBuilder allWords = new StringBuilder(); 1019 for (String word : StringUtils.split(escapedWords)) 1020 { 1021 if (allWords.length() > 0) 1022 { 1023 allWords.append(' '); 1024 } 1025 allWords.append('+').append(word); 1026 } 1027 1028 // queries.add(new FullTextQuery(allWords.toString(), language, 1029 // Operator.SEARCH)); 1030 1031 Query query = new FullTextQuery(allWords.toString(), language, Operator.SEARCH); 1032 Query contentQuery = new PageContentQuery(query); 1033 1034 queries.add(new OrQuery(query, contentQuery)); 1035 } 1036 } 1037 1038 // exact wording or phrase 1039 private void _addExactWordingTextFieldQuery(Collection<Query> queries, String language, Request request) throws IllegalArgumentException 1040 { 1041 String exact = request.getParameter("exact-wording"); 1042 1043 if (StringUtils.isNotBlank(exact)) 1044 { 1045 String escapedExact = ClientUtils.escapeQueryChars(exact); 1046 1047 Query query = new FullTextQuery(escapedExact, language, Operator.EQ); 1048 Query contentQuery = new PageContentQuery(query); 1049 1050 queries.add(new OrQuery(query, contentQuery)); 1051 } 1052 } 1053 1054 // none of this word 1055 private void _addNoWordsTextFieldQuery(Collection<Query> queries, String language, Request request) throws IllegalArgumentException 1056 { 1057 String noWords = request.getParameter("no-words"); 1058 1059 if (StringUtils.isNotBlank(noWords)) 1060 { 1061 String escapedWords = ClientUtils.escapeQueryChars(noWords); 1062 1063 StringBuilder allWords = new StringBuilder(); 1064 for (String word : StringUtils.split(escapedWords)) 1065 { 1066 if (allWords.length() > 0) 1067 { 1068 allWords.append(' '); 1069 } 1070 allWords.append('+').append(word); 1071 } 1072 1073 // queries.add(new NotQuery(new FullTextQuery(allWords.toString(), 1074 // language, Operator.SEARCH))); 1075 1076 Query query = new NotQuery(new FullTextQuery(allWords.toString(), language, Operator.SEARCH)); 1077 Query contentQuery = new PageContentQuery(query); 1078 1079 queries.add(new OrQuery(query, contentQuery)); 1080 } 1081 } 1082 1083 private void _addContentTypeQuery(Collection<Query> queries, Request request) 1084 { 1085 Collection<String> cTypes = new ArrayList<>(getContentTypes(request)); 1086 1087 // Resource is handled in the "document types" filter. 1088 cTypes.remove("resource"); 1089 1090 if (!cTypes.isEmpty()) 1091 { 1092 queries.add(new PageContentQuery(new ContentTypeQuery(cTypes))); 1093 } 1094 } 1095 1096 private void _addTagsQuery(Collection<Query> queries, Request request) 1097 { 1098 String size = request.getParameter("tags-size"); 1099 if (!StringUtils.isEmpty(size)) 1100 { 1101 @SuppressWarnings("unchecked") 1102 Map<String, Object> parentContext = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); 1103 if (parentContext == null) 1104 { 1105 parentContext = Collections.emptyMap(); 1106 } 1107 1108 boolean isStrictSearch = parameters.getParameterAsBoolean("strict-search-on-tags", true); 1109 1110 int nbCat = Integer.parseInt(size); 1111 for (int i = 1; i < nbCat + 1; i++) 1112 { 1113 String[] tags = request.getParameterValues("tags-" + i); 1114 if (tags != null && tags.length > 0 && !(tags.length == 1 && tags[0].equals(""))) 1115 { 1116 List<Query> tagQueries = new ArrayList<>(); 1117 1118 for (String tag : tags) 1119 { 1120 String[] values = StringUtils.split(tag, ','); 1121 1122 tagQueries.add(new TagQuery(Operator.EQ, !isStrictSearch, values)); 1123 } 1124 1125 queries.add(new PageContentQuery(new OrQuery(tagQueries))); 1126 } 1127 } 1128 } 1129 } 1130 1131 private void _addPagesQuery(Collection<Query> queries, Request request) 1132 { 1133 List<Query> pageQueries = new ArrayList<>(); 1134 1135 String[] pages = request.getParameterValues("pages"); 1136 if (pages != null && pages.length > 0 && !(pages.length == 1 && pages[0].equals(""))) 1137 { 1138 for (String pageIds : pages) 1139 { 1140 for (String pageId : StringUtils.split(pageIds, ",")) 1141 { 1142 pageQueries.add(new PageQuery(pageId, true)); 1143 } 1144 } 1145 } 1146 1147 queries.add(new OrQuery(pageQueries)); 1148 } 1149 1150 private void _addDateQuery(Collection<Query> queries, Request request) 1151 { 1152 String startDateId = parameters.getParameter("startDate", ""); 1153 String endDateId = parameters.getParameter("endDate", ""); 1154 1155 String startDateStr = request.getParameter("startDate"); 1156 String endDateStr = request.getParameter("endDate"); 1157 1158 // We check if startDateStr < endDateStr. If not, we swap. 1159 if (StringUtils.isNotBlank(endDateStr) && StringUtils.isNotBlank(startDateStr) && startDateStr.compareTo(endDateStr) > 0) 1160 { 1161 String dateAux = startDateStr; 1162 startDateStr = endDateStr; 1163 endDateStr = dateAux; 1164 } 1165 1166 String startPropId = startDateId.equals("last-modified") ? SolrFieldNames.LAST_MODIFIED : startDateId; 1167 String endPropId = endDateId.equals("last-modified") ? SolrFieldNames.LAST_MODIFIED : endDateId; 1168 1169 if (StringUtils.isNotBlank(endDateStr)) 1170 { 1171 Date endDate = _toDate(endDateStr); 1172 // Join for returning page documents 1173 queries.add(new PageContentQuery(new DateQuery(startPropId, Operator.LE, endDate))); 1174 } 1175 1176 if (StringUtils.isNotBlank(startDateStr)) 1177 { 1178 Date startDate = _toDate(startDateStr); 1179 1180 if (startDate != null) 1181 { 1182 // Two cases are possible : 1183 // An event could have an end date (query 1) 1184 // If not, the start date is also the end date (query 2) 1185 List<Query> endDateQueries = new ArrayList<>(); 1186 1187 if (StringUtils.isNotBlank(endPropId)) 1188 { 1189 endDateQueries.add(new DateQuery(endPropId, Operator.GE, startDate)); 1190 } 1191 endDateQueries.add(new DateQuery(startPropId, Operator.GE, startDate)); 1192 1193 // Join for returning page documents 1194 queries.add(new PageContentQuery(new OrQuery(endDateQueries))); 1195 } 1196 } 1197 } 1198 1199 private Date _toDate(String dateStr) 1200 { 1201 try 1202 { 1203 LocalDate dt = LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE); 1204 return DateUtils.asDate(dt); 1205 } 1206 catch (DateTimeParseException e) 1207 { 1208 getLogger().error("Invalid date format " + dateStr, e); 1209 } 1210 return null; 1211 } 1212 1213}