001/* 002 * Copyright 2015 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.web.indexing.solr; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Date; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Optional; 027import java.util.Set; 028 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.context.Context; 031import org.apache.avalon.framework.context.ContextException; 032import org.apache.avalon.framework.context.Contextualizable; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.avalon.framework.service.Serviceable; 036import org.apache.cocoon.components.ContextHelper; 037import org.apache.cocoon.environment.Request; 038import org.apache.commons.lang3.ArrayUtils; 039import org.apache.solr.client.solrj.SolrClient; 040import org.apache.solr.client.solrj.SolrServerException; 041import org.apache.solr.client.solrj.response.UpdateResponse; 042import org.apache.solr.common.SolrInputDocument; 043import org.apache.solr.common.SolrInputField; 044 045import org.ametys.cms.content.indexing.solr.SolrContentIndexer; 046import org.ametys.cms.content.indexing.solr.SolrFieldNames; 047import org.ametys.cms.content.indexing.solr.SolrIndexer; 048import org.ametys.cms.content.indexing.solr.SolrResourceIndexer; 049import org.ametys.cms.contenttype.ContentConstants; 050import org.ametys.cms.contenttype.ContentTypesHelper; 051import org.ametys.cms.contenttype.MetadataDefinition; 052import org.ametys.cms.contenttype.RepeaterDefinition; 053import org.ametys.cms.contenttype.indexing.IndexingField; 054import org.ametys.cms.contenttype.indexing.IndexingModel; 055import org.ametys.cms.contenttype.indexing.MetadataIndexingField; 056import org.ametys.cms.indexing.IndexingException; 057import org.ametys.cms.indexing.solr.AdditionalPropertyIndexer; 058import org.ametys.cms.indexing.solr.AdditionalPropertyIndexerExtensionPoint; 059import org.ametys.cms.repository.Content; 060import org.ametys.cms.search.query.AndQuery; 061import org.ametys.cms.search.query.DocumentTypeQuery; 062import org.ametys.cms.search.query.JoinQuery; 063import org.ametys.cms.search.query.OrQuery; 064import org.ametys.cms.search.query.Query; 065import org.ametys.cms.search.query.QuerySyntaxException; 066import org.ametys.cms.search.solr.SolrClientProvider; 067import org.ametys.cms.tag.Tag; 068import org.ametys.cms.tag.TagHelper; 069import org.ametys.cms.tag.TagProviderExtensionPoint; 070import org.ametys.plugins.explorer.resources.Resource; 071import org.ametys.plugins.explorer.resources.ResourceCollection; 072import org.ametys.plugins.repository.AmetysObject; 073import org.ametys.plugins.repository.AmetysObjectResolver; 074import org.ametys.plugins.repository.AmetysRepositoryException; 075import org.ametys.plugins.repository.RepositoryConstants; 076import org.ametys.plugins.repository.metadata.CompositeMetadata; 077import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 078import org.ametys.runtime.plugin.component.AbstractLogEnabled; 079import org.ametys.web.WebConstants; 080import org.ametys.web.repository.page.Page; 081import org.ametys.web.repository.page.Page.PageType; 082import org.ametys.web.repository.page.Zone; 083import org.ametys.web.repository.page.ZoneItem; 084import org.ametys.web.repository.page.ZoneItem.ZoneType; 085import org.ametys.web.repository.site.Site; 086import org.ametys.web.repository.sitemap.Sitemap; 087import org.ametys.web.search.query.PageAttachmentQuery; 088import org.ametys.web.search.query.PageQuery; 089import org.ametys.web.service.Service; 090import org.ametys.web.service.ServiceExtensionPoint; 091 092/** 093 * Component responsible for indexing a page with all its contents. 094 */ 095public class SolrPageIndexer extends AbstractLogEnabled implements Component, Serviceable, SolrWebFieldNames, Contextualizable 096{ 097 /** The avalon role. */ 098 public static final String ROLE = SolrPageIndexer.class.getName(); 099 100 /** The Solr client provider */ 101 protected SolrClientProvider _solrClientProvider; 102 /** The Solr indexer */ 103 protected SolrIndexer _solrIndexer; 104 /** Solr Ametys contents indexer */ 105 protected SolrContentIndexer _solrContentIndexer; 106 /** Solr Ametys resources indexer */ 107 protected SolrResourceIndexer _solrResourceIndexer; 108 /** The additional property indexer extension point. */ 109 protected AdditionalPropertyIndexerExtensionPoint _additionalPropertiesIndexerEP; 110 /** The tag provider extension point. */ 111 protected TagProviderExtensionPoint _tagProviderEP; 112 113 /** The service extension point. */ 114 protected ServiceExtensionPoint _serviceExtensionPoint; 115 /** The Ametys object resolver*/ 116 protected AmetysObjectResolver _ametysObjectResolver; 117 /** The avalon context */ 118 protected Context _context; 119 120 private ContentTypesHelper _cTypesHelper; 121 122 @Override 123 public void service(ServiceManager manager) throws ServiceException 124 { 125 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 126 _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE); 127 _solrContentIndexer = (SolrContentIndexer) manager.lookup(SolrContentIndexer.ROLE); 128 _solrResourceIndexer = (SolrResourceIndexer) manager.lookup(SolrResourceIndexer.ROLE); 129 _solrClientProvider = (SolrClientProvider) manager.lookup(SolrClientProvider.ROLE); 130 _serviceExtensionPoint = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE); 131 _additionalPropertiesIndexerEP = (AdditionalPropertyIndexerExtensionPoint) manager.lookup(AdditionalPropertyIndexerExtensionPoint.ROLE); 132 _tagProviderEP = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE); 133 _cTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 134 } 135 136 public void contextualize(Context context) throws ContextException 137 { 138 _context = context; 139 } 140 141 /** 142 * Index a page and eventually its children, recursively, in all workspaces and commit<br> 143 * By default, children pages will be actually indexed if indexRecursively is true and if those pages are not already indexed. 144 * @param pageId the page to be indexed. 145 * @param indexRecursively to also process children pages. 146 * @param indexAttachments to index page attachments 147 * @throws Exception if an error occurs during indexation. 148 */ 149 public void indexPage(String pageId, boolean indexRecursively, boolean indexAttachments) throws Exception 150 { 151 indexPage(pageId, RepositoryConstants.DEFAULT_WORKSPACE, indexRecursively, indexAttachments, true); 152 indexPage(pageId, WebConstants.LIVE_WORKSPACE, indexRecursively, indexAttachments, true); 153 } 154 155 /** 156 * Index a page and eventually its children, recursively.<br> 157 * By default, children pages will be actually indexed if indexRecursively is true and if those pages are not already indexed. 158 * @param pageId the page to be indexed. 159 * @param workspaceName the workspace where to index 160 * @param indexRecursively to also process children pages. 161 * @param indexAttachments to index page attachments 162 * @param commit Commit the indexation 163 * @throws IndexingException if an error occurs during indexation. 164 */ 165 public void indexPage(String pageId, String workspaceName, boolean indexRecursively, boolean indexAttachments, boolean commit) throws IndexingException 166 { 167 Request request = ContextHelper.getRequest(_context); 168 169 // Retrieve the current workspace. 170 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 171 // Retrieve the current site name. 172 String currentSiteName = (String) request.getAttribute("siteName"); 173 174 try 175 { 176 // Force the workspace. 177 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 178 179 getLogger().debug("Indexing page: {}", pageId); 180 181 if (_ametysObjectResolver.hasAmetysObjectForId(pageId)) // In 'live' the page may not exist 182 { 183 Page page = _ametysObjectResolver.resolveById(pageId); 184 _indexPage(page, workspaceName, indexRecursively, indexAttachments); 185 186 if (commit) 187 { 188 _solrIndexer.commit(workspaceName); 189 } 190 } 191 } 192 catch (AmetysRepositoryException | SolrServerException | IOException e) 193 { 194 String error = String.format("Failed to index page %s in workspace %s", pageId, workspaceName); 195 getLogger().error(error, e); 196 throw new IndexingException(error, e); 197 } 198 finally 199 { 200 // Restore the site name. 201 request.setAttribute("siteName", currentSiteName); 202 // Restore context 203 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 204 } 205 } 206 207 private void _indexPage(Page page, String workspaceName, boolean indexRecursively, boolean indexAttachments) throws IndexingException 208 { 209 getLogger().info("Indexing page: {} in workspace '{}'", page, workspaceName); 210 211 SolrInputDocument document = new SolrInputDocument(); 212 213 try 214 { 215 // Prepare the solr input document by adding fields. 216 _populatePageDocument(page, document); 217 218 // Set the additional properties in the document. 219 _populateAdditionalProperties(page, document); 220 221 // Indexation of AmetysObject property 222 document.addField(SolrFieldNames.IS_AMETYS_OBJECT, true); 223 224 // Indexation of the document 225 _indexPageDocument(page, document, workspaceName); 226 227 // Index page attachments documents 228 if (indexAttachments) 229 { 230 indexPageAttachments(page.getRootAttachments(), page); 231 } 232 } 233 catch (Exception e) 234 { 235 String error = String.format("Failed to index page %s in workspace %s", page.getId(), workspaceName); 236 getLogger().error(error, e); 237 throw new IndexingException(error, e); 238 } 239 240 if (indexRecursively) 241 { 242 for (Page child : page.getChildrenPages()) 243 { 244 // FIXME index child pages if (and only if) not indexed... see original source. 245// indexPage(child, false, indexRecursively); 246// indexPage(child, false); 247 _indexPage(child, workspaceName, indexRecursively, indexAttachments); 248 } 249 } 250 } 251 252 /** 253 * Populate the solr input document by adding fields to index. 254 * @param page the page to index. 255 * @param document the solr input document 256 * @throws Exception if something goes wrong when processing the indexation of the page 257 */ 258 protected void _populatePageDocument(Page page, SolrInputDocument document) throws Exception 259 { 260 Sitemap sitemap = page.getSitemap(); 261 String sitemapName = sitemap.getName(); 262 Site site = page.getSite(); 263 String siteName = site.getName(); 264 String pageId = page.getId(); 265 String pageTitle = page.getTitle(); 266 String pageLongTitle = page.getLongTitle(); 267 String language = sitemapName; 268 269 // Page id and type 270 document.addField(SolrFieldNames.ID, pageId); 271 document.addField(SolrFieldNames.DOCUMENT_TYPE, SolrWebFieldNames.TYPE_PAGE); 272 273 // Fulltext 274 SolrContentIndexer.indexFulltextValue(document, pageTitle, language); 275 if (!pageTitle.equals(pageLongTitle)) 276 { 277 SolrContentIndexer.indexFulltextValue(document, pageLongTitle, language); 278 } 279 280 // Page title 281 _indexStringFields(document, pageId, PAGE_TITLE, pageTitle, language); 282 // Page long title 283 _indexStringFields(document, pageId, PAGE_LONG_TITLE, pageLongTitle, language); 284 // Title for sorting 285 document.addField(TITLE_SORT, pageTitle); 286 287 document.addField(TEMPLATE, page.getTemplate()); 288 document.addField(PAGE_TYPE, page.getType().name()); 289 document.addField(PAGE_DEPTH, page.getDepth()); 290 291 // Contents (page title shoud be indexed before because the main content can override it). 292 _populatePageContentsDocument(page, document); 293 294 // Parents of the page 295 List<String> ancestorIds = new ArrayList<>(); 296 AmetysObject parent = page.getParent(); 297 while (parent instanceof Page) 298 { 299 ancestorIds.add(parent.getId()); 300 parent = parent.getParent(); 301 } 302 document.addField(PAGE_ANCESTOR_IDS, ancestorIds); 303 304 document.addField(SITE_NAME, siteName); 305 document.addField(SITEMAP_NAME, sitemapName); 306 document.addField(SITE_TYPE, site.getType()); 307 308 // Page tags (strict and tags including ancestor pages). 309 document.addField(SolrFieldNames.TAGS, page.getTags()); 310 document.addField(SolrFieldNames.ALL_TAGS, _getTagsWithAncestors(page)); 311 312 // Page last modification date - Store.YES, Index.ANALYZED 313 Date lastModified = _getLastModificationDate(page); 314 if (lastModified != null) 315 { 316 document.addField(LAST_MODIFIED + "_dt", SolrIndexer.dateFormat().format(lastModified)); 317 } 318 319 // Page last validation date - Store.YES, Index.ANALYZED 320 Date lastValidation = _getLastValidationDate(page); 321 if (lastValidation != null) 322 { 323 document.addField(LAST_VALIDATION, SolrIndexer.dateFormat().format(lastValidation)); 324 } 325 326 // date for sorting 327 SolrInputField dateField = document.getField(DATE_FOR_SORTING); 328 if (dateField == null) 329 { 330 Collection<Object> oDateValues = document.getFieldValues(CONTENT_INTERESTING_DATES); 331 if (oDateValues != null && !oDateValues.isEmpty()) 332 { 333 document.setField(DATE_FOR_SORTING, oDateValues.iterator().next()); 334 } 335 } 336 337 // Attachments 338 _solrResourceIndexer.indexResourceCollection(page.getRootAttachments(), document, language); 339 Optional.ofNullable(page.getRootAttachments()).map(AmetysObject::getId).ifPresent(id -> document.addField(PAGE_OUTGOING_REFEERENCES_RESOURCE_IDS, id)); 340 } 341 342 343 private void _indexStringFields(SolrInputDocument document, String documentId, String fieldName, String fieldValue, String language) 344 { 345 String possiblyTruncatedValue = SolrIndexer.truncateUtf8StringValue(fieldValue, getLogger(), documentId, fieldName); 346 347 document.addField(fieldName, possiblyTruncatedValue); 348 document.addField(fieldName + "_txt_" + language, fieldValue); 349 document.addField(fieldName + "_txt_stemmed_" + language, fieldValue); 350 document.addField(fieldName + "_txt_ws_" + language, fieldValue); 351 352 document.addField(fieldName + "_s_lower", possiblyTruncatedValue.toLowerCase()); 353 document.addField(fieldName + "_s_ws", fieldValue.toLowerCase()); 354 document.addField(fieldName + "_txt", fieldValue); 355 } 356 /** 357 * Get all the page tags with their ancestors. 358 * @param page The page. 359 * @return All the page tags with their ancestors. 360 */ 361 protected Set<String> _getTagsWithAncestors(Page page) 362 { 363 Set<String> allTags = new HashSet<>(page.getTags()); 364 365 Map<String, Object> tagParams = Collections.singletonMap("siteName", page.getSiteName()); 366 367 for (String tagName : page.getTags()) 368 { 369 allTags.add(tagName); 370 371 // Get the ancestor tags 372 Tag tag = _tagProviderEP.getTag(tagName, tagParams); 373 for (Tag ancestor : TagHelper.getAncestors(tag, false)) 374 { 375 allTags.add(ancestor.getName()); 376 } 377 } 378 379 return allTags; 380 } 381 382 /** 383 * Index the content of the page.<p> 384 * @param page the page to index. 385 * @param document the document to populate. 386 * @throws Exception if an error occurs. 387 */ 388 protected void _populatePageContentsDocument(Page page, SolrInputDocument document) throws Exception 389 { 390 if (page.getType() == PageType.CONTAINER) 391 { 392 for (Zone zone : page.getZones()) 393 { 394 for (ZoneItem zoneItem : zone.getZoneItems()) 395 { 396 if (zoneItem.getType() == ZoneType.CONTENT) 397 { 398 try 399 { 400 Content content = zoneItem.getContent(); 401 document.addField(CONTENT_IDS, content.getId()); 402 403 for (String cType : content.getTypes()) 404 { 405 document.addField(PAGE_CONTENT_TYPES, cType); 406 document.addField(PAGE_CONTENT_TYPES + "_s_dv", cType); // facets 407 } 408 409 _indexFacetableField(content, document); 410 } 411 catch (AmetysRepositoryException e) 412 { 413 getLogger().error("Failed to index content referenced in the page {}/{}/{} ({} in zoneitem {})", page.getSiteName(), page.getSitemapName(), page.getPathInSitemap(), page.getId(), zoneItem.getId(), e); 414 } 415 } 416 else if (zoneItem.getType() == ZoneType.SERVICE) 417 { 418 try 419 { 420 String serviceId = zoneItem.getServiceId(); 421 document.addField(SERVICE_IDS, serviceId); 422 423 Service service = _serviceExtensionPoint.getExtension(serviceId); 424 if (service == null) 425 { 426 getLogger().error("The service id '{}' does not exist. It is referenced in the page {}/{}/{} ({} in zoneitem {})", serviceId, page.getSiteName(), page.getSitemapName(), page.getPathInSitemap(), page.getId(), zoneItem.getId()); 427 } 428 else 429 { 430 service.index(zoneItem, document); 431 } 432 } 433 catch (AmetysRepositoryException e) 434 { 435 getLogger().error("Failed to index service referenced in the page {}/{}/{} ({} in zoneitem {})", page.getSiteName(), page.getSitemapName(), page.getPathInSitemap(), page.getId(), zoneItem.getId(), e); 436 } 437 438 } 439 } 440 } 441 } 442 } 443 444 /** 445 * Index the facetable fields of a content into the page solr document 446 * @param content The content 447 * @param document The main page solr document. 448 */ 449 protected void _indexFacetableField(Content content, SolrInputDocument document) 450 { 451 IndexingModel indexingModel = null; 452 try 453 { 454 indexingModel = _cTypesHelper.getIndexingModel(content); 455 } 456 catch (RuntimeException e) 457 { 458 getLogger().error("indexContent > Error getting the indexing model of content " + content.getId(), e); 459 throw e; 460 } 461 462 for (IndexingField field : indexingModel.getFields()) 463 { 464 if (field instanceof MetadataIndexingField) 465 { 466 String metadataPath = ((MetadataIndexingField) field).getMetadataPath(); 467 String[] pathSegments = metadataPath.split(ContentConstants.METADATA_PATH_SEPARATOR); 468 469 MetadataDefinition definition = _cTypesHelper.getMetadataDefinition(pathSegments[0], content.getTypes(), content.getMixinTypes()); 470 if (definition != null) 471 { 472 _findAndIndexFacetableField(pathSegments, content.getLanguage(), content.getMetadataHolder(), definition, field, document); 473 } 474 } 475 } 476 } 477 478 /** 479 * Index the facetable fields of a content into the page solr document 480 * @param pathSegments The path of metadata 481 * @param lang The language 482 * @param metadata The parent composite metadata 483 * @param definition The metadata definition 484 * @param field The indexing field 485 * @param pageDocument The Solr page document 486 */ 487 protected void _findAndIndexFacetableField(String[] pathSegments, String lang, CompositeMetadata metadata, MetadataDefinition definition, IndexingField field, SolrInputDocument pageDocument) 488 { 489 String currentFieldName = pathSegments[0]; 490 491 if (!metadata.hasMetadata(currentFieldName)) 492 { 493 // Nothing to do 494 return; 495 } 496 497 switch (definition.getType()) 498 { 499 case STRING: 500 if (definition.getEnumerator() != null) 501 { 502 String[] strValues = metadata.getStringArray(currentFieldName, new String[0]); 503 for (String value : strValues) 504 { 505 pageDocument.addField(FACETABLE_CONTENT_FIELD_PREFIX + currentFieldName + "_s_dv", value); 506 } 507 } 508 break; 509 case CONTENT: 510 String[] contentIds = metadata.getStringArray(currentFieldName, new String[0]); 511 for (String contentId : contentIds) 512 { 513 pageDocument.addField(FACETABLE_CONTENT_FIELD_PREFIX + currentFieldName + "_s_dv", contentId); 514 } 515 break; 516 case COMPOSITE: 517 if (pathSegments.length > 1) 518 { 519 String[] followingSegments = ArrayUtils.subarray(pathSegments, 1, pathSegments.length); 520 521 CompositeMetadata composite = metadata.getCompositeMetadata(currentFieldName); 522 if (definition instanceof RepeaterDefinition) 523 { 524 String[] entries = composite.getMetadataNames(); 525 for (String entry : entries) 526 { 527 _findAndIndexFacetableField(followingSegments, lang, composite.getCompositeMetadata(entry), definition.getMetadataDefinition(followingSegments[0]), field, pageDocument); 528 } 529 } 530 else 531 { 532 _findAndIndexFacetableField(followingSegments, lang, composite, definition.getMetadataDefinition(followingSegments[0]), field, pageDocument); 533 } 534 } 535 536 break; 537 default: 538 break; 539 540 } 541 } 542 543 /** 544 * Computes the last modification date of a page. 545 * @param page the page. 546 * @return the last modification date or <code>null</code>. 547 */ 548 protected Date _getLastModificationDate(Page page) 549 { 550 Date lastModified = null; 551 552 if (page.getType() == PageType.CONTAINER) 553 { 554 for (Zone zone : page.getZones()) 555 { 556 for (ZoneItem zoneItem : zone.getZoneItems()) 557 { 558 switch (zoneItem.getType()) 559 { 560 case SERVICE: 561 // A service has no last modification date 562 break; 563 case CONTENT: 564 try 565 { 566 Date contentLastModified = zoneItem.getContent().getLastModified(); 567 568 if (lastModified == null || contentLastModified.after(lastModified)) 569 { 570 // Keep the latest modification date 571 lastModified = contentLastModified; 572 } 573 } 574 catch (AmetysRepositoryException e) 575 { 576 getLogger().error("Failed to index last modification date for content in page {}/{}/{} ({} in zoneitem {})", page.getSiteName(), page.getSitemapName(), page.getPathInSitemap(), page.getId(), zoneItem.getId(), e); 577 } 578 break; 579 default: 580 break; 581 } 582 } 583 } 584 } 585 586 return lastModified; 587 } 588 589 /** 590 * Computes the last validation date of a page. 591 * @param page the page. 592 * @return the last validation date or <code>null</code>. 593 */ 594 protected Date _getLastValidationDate(Page page) 595 { 596 Date lastValidated = null; 597 598 if (page.getType() == PageType.CONTAINER) 599 { 600 for (Zone zone : page.getZones()) 601 { 602 for (ZoneItem zoneItem : zone.getZoneItems()) 603 { 604 switch (zoneItem.getType()) 605 { 606 case SERVICE: 607 // A service has no last validation date 608 break; 609 case CONTENT: 610 try 611 { 612 Date contentLastValidation = zoneItem.getContent().getLastValidationDate(); 613 614 if (contentLastValidation != null && (lastValidated == null || contentLastValidation.after(lastValidated))) 615 { 616 // Keep the latest modification date 617 lastValidated = contentLastValidation; 618 } 619 } 620 catch (AmetysRepositoryException e) 621 { 622 getLogger().error("Failed to index last validation date for content in page {}/{}/{} ({} in zoneitem {})", page.getSiteName(), page.getSitemapName(), page.getPathInSitemap(), page.getId(), zoneItem.getId(), e); 623 } 624 break; 625 default: 626 break; 627 } 628 } 629 } 630 } 631 632 return lastValidated; 633 } 634 635 /** 636 * Populate the solr input document by adding fields to index. 637 * @param page the page to index. 638 * @param document the solr input document 639 * @throws Exception if something goes wrong when processing the indexation of the page 640 */ 641 protected void _populateAdditionalProperties(Page page, SolrInputDocument document) throws Exception 642 { 643 Collection<AdditionalPropertyIndexer> indexers = _additionalPropertiesIndexerEP.getIndexers("page"); 644 for (AdditionalPropertyIndexer indexer : indexers) 645 { 646 indexer.index(page, document); 647 } 648 } 649 650 /** 651 * Index page attachments as new entries in the index. 652 * @param collection the collection of attachments 653 * @param page the page whose attachments will be indexed 654 * @throws Exception if something goes wrong when indexing the attachments of the page 655 */ 656 public void indexPageAttachments(ResourceCollection collection, Page page) throws Exception 657 { 658 if (collection == null) 659 { 660 return; 661 } 662 663 for (AmetysObject object : collection.getChildren()) 664 { 665 if (object instanceof ResourceCollection) 666 { 667 indexPageAttachments((ResourceCollection) object, page); 668 } 669 else if (object instanceof Resource) 670 { 671 Resource resource = (Resource) object; 672 indexPageAttachment(resource, page); 673 } 674 } 675 } 676 677 /** 678 * Index a page attachment 679 * @param resource the page attachment as a {@link Resource} 680 * @param page the page whose attachment is going to be indexed 681 * @throws Exception if something goes wrong when processing the indexation of the page attachment 682 */ 683 public void indexPageAttachment(Resource resource, Page page) throws Exception 684 { 685 SolrInputDocument document = new SolrInputDocument(); 686 687 // Prepare resource doc 688 _indexPageAttachment(resource, document, page); 689 690 // Indexation of the document 691 _indexResourceDocument(resource, document); 692 } 693 694 private void _indexPageAttachment(Resource resource, SolrInputDocument document, Page page) throws Exception 695 { 696 String language = page.getSitemapName(); 697 698 _solrResourceIndexer.indexResource(resource, document, TYPE_PAGE_RESOURCE, language); 699 700 Site site = page.getSite(); 701 // site name - Store.YES, Index.NOT_ANALYZED 702 document.addField(SolrWebFieldNames.SITE_NAME, site.getName()); 703 704 // site type - Store.YES, Index.NOT_ANALYZED 705 document.addField(SolrWebFieldNames.SITE_TYPE, site.getType()); 706 707 // Added for Solr. 708 // Page site map name - Store.YES, Index.NOT_ANALYZED 709 document.addField(SITEMAP_NAME, page.getSitemapName()); 710 711 // Need the id of the page for unindexing attachment during the unindexing of the page 712 document.addField(ATTACHMENT_PAGE_ID, page.getId()); 713 } 714 715 /** 716 * Index a populated solr input document of type Page. 717 * @param page the page from which the input document is created 718 * @param document the input document to add to the solr index 719 * @param workspaceName The workspace name 720 * @throws SolrServerException if there is an error on the Solr server 721 * @throws IOException if there is a communication error with the server 722 */ 723 protected void _indexPageDocument(Page page, SolrInputDocument document, String workspaceName) throws SolrServerException, IOException 724 { 725 // Retrieve appropriate solr client 726 String collectionName = _solrClientProvider.getCollectionName(workspaceName); 727 SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName); 728 729 // Add document 730 UpdateResponse solrResponse = solrClient.add(collectionName, document); 731 int status = solrResponse.getStatus(); 732 733 if (status != 0) 734 { 735 throw new IOException("Ametys Page indexing - Expecting status code of '0' in the Solr response but got : '" + status + "'. Page id : " + page.getId()); 736 } 737 738 getLogger().debug("Successful page indexing. Page identifier : {}", page.getId()); 739 } 740 741 /** 742 * Index a populated solr input document of type Resource. 743 * @param resource the resource from which the input document is created 744 * @param document the input document 745 * @throws SolrServerException if there is an error on the server 746 * @throws IOException if there is a communication error with the server 747 */ 748 protected void _indexResourceDocument(Resource resource, SolrInputDocument document) throws SolrServerException, IOException 749 { 750 // Retrieve appropriate solr client 751 Request request = ContextHelper.getRequest(_context); 752 String workspaceName = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 753 String collectionName = _solrClientProvider.getCollectionName(workspaceName); 754 SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName); 755 756 // Add document 757 UpdateResponse solrResponse = solrClient.add(collectionName, document); 758 int status = solrResponse.getStatus(); 759 760 if (status != 0) 761 { 762 throw new IOException("Ametys Page indexing - Expecting status code of '0' in the Solr response but got : '" + status + "'. Resource id : " + resource.getId()); 763 } 764 765 getLogger().debug("Successful resource indexing. Resource identifier : {}", resource.getId()); 766 } 767 768 /////////////////////////////////////////////////////////////////////////// 769 770 /** 771 * Un-index a page by its ID for all workspaces and commit 772 * @param pageId The page ID. 773 * @param unindexRecursively also unindex child pages if requested. 774 * @param unindexAttachments also unindex page attachments 775 * @throws Exception if an error occurs during index update. 776 */ 777 public void unindexPage(String pageId, boolean unindexRecursively, boolean unindexAttachments) throws Exception 778 { 779 unindexPage(pageId, RepositoryConstants.DEFAULT_WORKSPACE, unindexRecursively, unindexAttachments, true); 780 unindexPage(pageId, WebConstants.LIVE_WORKSPACE, unindexRecursively, unindexAttachments, true); 781 } 782 783 /** 784 * De-index a page (and optionally its children pages). 785 * @param pageId the page to be de-indexed. 786 * @param workspaceName The workspace where to work in 787 * @param unindexRecursively also unindex child pages if requested. 788 * @param unindexAttachments also unindex page attachments 789 * @param commit Commit the operator to Solr 790 * @throws Exception if an error occurs during index update. 791 */ 792 public void unindexPage(String pageId, String workspaceName, boolean unindexRecursively, boolean unindexAttachments, boolean commit) throws Exception 793 { 794 Request request = ContextHelper.getRequest(_context); 795 796 // Retrieve the current workspace. 797 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 798 // Retrieve the current site name. 799 String currentSiteName = (String) request.getAttribute("siteName"); 800 801 try 802 { 803 // Force the workspace. 804 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 805 806 getLogger().debug("Unindexing page: {}", pageId); 807 808 _unindexPageDocument(pageId, workspaceName, unindexRecursively, unindexAttachments); 809 810 if (commit) 811 { 812 _solrIndexer.commit(workspaceName); 813 } 814 } 815 catch (Exception e) 816 { 817 String error = String.format("Failed to unindex page %s in workspace %s", pageId, workspaceName); 818 getLogger().error(error, e); 819 throw new IndexingException(error, e); 820 } 821 finally 822 { 823 // Restore the site name. 824 request.setAttribute("siteName", currentSiteName); 825 // Restore context 826 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 827 } 828 } 829 830 /** 831 * Deindex a document of type Page. Also deindex attachments of a page 832 * @param pageId the id of the page to deindex 833 * @param workspaceName The workspace name 834 * @param unindexRecursively also unindex child pages if requested. 835 * @param unindexAttachments also unindex page attachments 836 * @throws SolrServerException if there is an error on the server 837 * @throws IOException if there is a communication error with the server 838 * @throws QuerySyntaxException if the uri query can't be built because of a syntax error. 839 */ 840 protected void _unindexPageDocument(String pageId, String workspaceName, boolean unindexRecursively, boolean unindexAttachments) throws SolrServerException, IOException, QuerySyntaxException 841 { 842 // Retrieve appropriate solr client 843 String collectionName = _solrClientProvider.getCollectionName(workspaceName); 844 SolrClient solrClient = _solrClientProvider.getUpdateClient(workspaceName); 845 846 getLogger().info("Unindexing page {} in workspace '{}'", pageId, workspaceName); 847 848 Query pages = new AndQuery(new DocumentTypeQuery(TYPE_PAGE), new PageQuery(pageId, unindexRecursively)); 849 Query query; 850 if (unindexRecursively && unindexAttachments) 851 { 852 // {!ametys join=pageId q=page-ancestorIds:"page://xxxx"} 853 Query joinQuery = new JoinQuery(() -> PAGE_ANCESTOR_IDS + ":\"" + pageId + "\"", ATTACHMENT_PAGE_ID); 854 Query attachments = new AndQuery(new DocumentTypeQuery(TYPE_PAGE_RESOURCE), new OrQuery(new PageAttachmentQuery(pageId), joinQuery)); 855 query = new OrQuery(attachments, pages); 856 } 857 else if (unindexAttachments) 858 { 859 Query attachments = new AndQuery(new DocumentTypeQuery(TYPE_PAGE_RESOURCE), new PageAttachmentQuery(pageId)); 860 query = new OrQuery(attachments, pages); 861 } 862 else 863 { 864 query = pages; 865 } 866 867 // Delete by query 868 UpdateResponse solrResponse = solrClient.deleteByQuery(collectionName, query.build()); 869 int status = solrResponse.getStatus(); 870 871 if (status != 0) 872 { 873 throw new IOException("Ametys Page de-indexing - Expecting status code of '0' in the Solr response but got : '" + status + "'. Page id : " + pageId); 874 } 875 876 getLogger().debug("Successful page de-indexing{}. Page identifier : {}", unindexRecursively ? " with its children" : "", pageId); 877 } 878 879 /////////////////////////////////////////////////////////////////////////// 880 881 /** 882 * Reindex a page by its ID for all workspaces and commit 883 * @param pageId The page ID. 884 * @param reindexRecursively also reindex child pages if requested. 885 * @param reindexAttachments also reindex page attachments 886 * @throws Exception if an error occurs during index update. 887 */ 888 public void reindexPage(String pageId, boolean reindexRecursively, boolean reindexAttachments) throws Exception 889 { 890 reindexPage(pageId, RepositoryConstants.DEFAULT_WORKSPACE, reindexRecursively, reindexAttachments, true); 891 reindexPage(pageId, WebConstants.LIVE_WORKSPACE, reindexRecursively, reindexAttachments, true); 892 } 893 894 895 /** 896 * Reindex a page by its ID. 897 * @param pageId The page ID. 898 * @param workspaceName The workspace where to work in 899 * @param reindexRecursively also reindex child pages if requested. 900 * @param reindexAttachments also reindex page attachments 901 * @param commit Commit the operator to Solr 902 * @throws IndexingException if an error occurs during index update. 903 */ 904 public void reindexPage(String pageId, String workspaceName, boolean reindexRecursively, boolean reindexAttachments, boolean commit) throws IndexingException 905 { 906 Request request = ContextHelper.getRequest(_context); 907 908 // Retrieve the current workspace. 909 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 910 // Retrieve the current site name. 911 String currentSiteName = (String) request.getAttribute("siteName"); 912 913 try 914 { 915 // Force the workspace. 916 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName); 917 918 getLogger().debug("Reindexing page: {}", pageId); 919 920 if (_ametysObjectResolver.hasAmetysObjectForId(pageId)) // In 'live' the page may not exist 921 { 922 Page page = _ametysObjectResolver.resolveById(pageId); 923 _unindexPageDocument(pageId, workspaceName, reindexRecursively, reindexAttachments); 924 _indexPage(page, workspaceName, reindexRecursively, reindexAttachments); 925 926 if (commit) 927 { 928 _solrIndexer.commit(workspaceName); 929 } 930 } 931 } 932 catch (AmetysRepositoryException | QuerySyntaxException | SolrServerException | IOException e) 933 { 934 String error = String.format("Failed to unindex page %s in workspace %s", pageId, workspaceName); 935 getLogger().error(error, e); 936 throw new IndexingException(error, e); 937 } 938 finally 939 { 940 // Restore the site name. 941 request.setAttribute("siteName", currentSiteName); 942 // Restore context 943 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 944 } 945 } 946}