001/* 002 * File : $Source$ 003 * Date : $Date$ 004 * Version: $Revision$ 005 * 006 * This library is part of OpenCms - 007 * the Open Source Content Management System 008 * 009 * Copyright (C) 2002 - 2009 Alkacon Software (http://www.alkacon.com) 010 * 011 * This library is free software; you can redistribute it and/or 012 * modify it under the terms of the GNU Lesser General Public 013 * License as published by the Free Software Foundation; either 014 * version 2.1 of the License, or (at your option) any later version. 015 * 016 * This library is distributed in the hope that it will be useful, 017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 019 * Lesser General Public License for more details. 020 * 021 * For further information about Alkacon Software, please see the 022 * company website: http://www.alkacon.com 023 * 024 * For further information about OpenCms, please see the 025 * project website: http://www.opencms.org 026 * 027 * You should have received a copy of the GNU Lesser General Public 028 * License along with this library; if not, write to the Free Software 029 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 030 */ 031 032package org.opencms.search.solr; 033 034import org.opencms.configuration.I_CmsXmlConfiguration; 035import org.opencms.file.CmsFile; 036import org.opencms.file.CmsObject; 037import org.opencms.file.CmsProperty; 038import org.opencms.file.CmsPropertyDefinition; 039import org.opencms.file.CmsResource; 040import org.opencms.file.types.CmsResourceTypeJsp; 041import org.opencms.file.types.CmsResourceTypeXmlContainerPage; 042import org.opencms.file.types.CmsResourceTypeXmlContent; 043import org.opencms.file.types.CmsResourceTypeXmlPage; 044import org.opencms.loader.CmsResourceManager; 045import org.opencms.main.CmsException; 046import org.opencms.main.CmsLog; 047import org.opencms.main.OpenCms; 048import org.opencms.search.CmsSearchIndexSource; 049import org.opencms.search.CmsSearchUtil; 050import org.opencms.search.I_CmsSearchDocument; 051import org.opencms.search.documents.CmsDocumentDependency; 052import org.opencms.search.extractors.I_CmsExtractionResult; 053import org.opencms.search.fields.CmsLuceneField; 054import org.opencms.search.fields.CmsSearchField; 055import org.opencms.search.fields.CmsSearchFieldConfiguration; 056import org.opencms.search.fields.CmsSearchFieldMapping; 057import org.opencms.search.fields.CmsSearchFieldMappingType; 058import org.opencms.search.fields.I_CmsSearchFieldMapping; 059import org.opencms.util.CmsStringUtil; 060import org.opencms.xml.CmsXmlContentDefinition; 061import org.opencms.xml.containerpage.CmsContainerElementBean; 062import org.opencms.xml.containerpage.CmsContainerPageBean; 063import org.opencms.xml.containerpage.CmsXmlContainerPage; 064import org.opencms.xml.containerpage.CmsXmlContainerPageFactory; 065import org.opencms.xml.content.I_CmsXmlContentHandler; 066 067import java.util.ArrayList; 068import java.util.Arrays; 069import java.util.Collection; 070import java.util.Collections; 071import java.util.Date; 072import java.util.HashMap; 073import java.util.List; 074import java.util.Locale; 075import java.util.Map; 076import java.util.Set; 077 078import org.apache.commons.logging.Log; 079import org.apache.solr.common.SolrInputDocument; 080 081/** 082 * The search field implementation for Solr.<p> 083 * 084 * @since 8.5.0 085 */ 086public class CmsSolrFieldConfiguration extends CmsSearchFieldConfiguration { 087 088 /** The log object for this class. */ 089 private static final Log LOG = CmsLog.getLog(CmsSolrFieldConfiguration.class); 090 091 /** The content locale for the indexed document is stored in order to save performance. */ 092 private Collection<Locale> m_contentLocales; 093 094 /** A list of Solr fields. */ 095 private Map<String, CmsSolrField> m_solrFields = new HashMap<String, CmsSolrField>(); 096 097 /** 098 * Default constructor.<p> 099 */ 100 public CmsSolrFieldConfiguration() { 101 102 super(); 103 } 104 105 /** 106 * Adds the additional fields to the configuration, if they are not null.<p> 107 * 108 * @param additionalFields the additional fields to add 109 */ 110 public void addAdditionalFields(List<CmsSolrField> additionalFields) { 111 112 if (additionalFields != null) { 113 for (CmsSolrField solrField : additionalFields) { 114 m_solrFields.put(solrField.getName(), solrField); 115 } 116 } 117 } 118 119 /** 120 * Returns all configured Solr fields.<p> 121 * 122 * @return all configured Solr fields 123 */ 124 public Map<String, CmsSolrField> getSolrFields() { 125 126 return Collections.unmodifiableMap(m_solrFields); 127 } 128 129 /** 130 * @see org.opencms.search.fields.CmsSearchFieldConfiguration#init() 131 */ 132 @Override 133 public void init() { 134 135 super.init(); 136 addAdditionalFields(); 137 } 138 139 /** 140 * @see org.opencms.search.fields.CmsSearchFieldConfiguration#appendAdditionalValuesToDcoument(org.opencms.search.I_CmsSearchDocument, org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.search.extractors.I_CmsExtractionResult, java.util.List, java.util.List) 141 */ 142 @Override 143 protected I_CmsSearchDocument appendAdditionalValuesToDcoument( 144 I_CmsSearchDocument document, 145 CmsObject cms, 146 CmsResource resource, 147 I_CmsExtractionResult extractionResult, 148 List<CmsProperty> properties, 149 List<CmsProperty> propertiesSearched) { 150 151 String mimeType = OpenCms.getResourceManager().getMimeType(resource.getName(), null); 152 if (mimeType != null) { 153 document.addSearchField(m_solrFields.get(CmsSearchField.FIELD_MIMETYPE), mimeType); 154 } 155 156 document.addSearchField(m_solrFields.get(CmsSearchField.FIELD_FILENAME), resource.getName()); 157 158 document.addSearchField(m_solrFields.get(CmsSearchField.FIELD_VERSION), "" + resource.getVersion()); 159 160 try { 161 if (CmsResourceTypeXmlContent.isXmlContent(resource)) { 162 I_CmsXmlContentHandler handler = CmsXmlContentDefinition.getContentHandlerForResource(cms, resource); 163 if ((handler != null) && handler.isContainerPageOnly()) { 164 if (document.getDocument() instanceof SolrInputDocument) { 165 SolrInputDocument doc = (SolrInputDocument)document.getDocument(); 166 doc.removeField(CmsSearchField.FIELD_SEARCH_EXCLUDE); 167 } else { 168 //TODO: Warning - but should not happen. 169 } 170 document.addSearchField(m_solrFields.get(CmsSearchField.FIELD_SEARCH_EXCLUDE), "true"); 171 } 172 } 173 } catch (CmsException e) { 174 LOG.error(e.getMessage(), e); 175 } 176 177 List<String> searchExcludeOptions = document.getMultivaluedFieldAsStringList( 178 CmsSearchField.FIELD_SEARCH_EXCLUDE); 179 if ((searchExcludeOptions == null) || searchExcludeOptions.isEmpty()) { 180 document.addSearchField(m_solrFields.get(CmsSearchField.FIELD_SEARCH_EXCLUDE), "false"); 181 } 182 if (resource.getRootPath().startsWith("/system") 183 || (CmsResourceTypeJsp.getJSPTypeId() == resource.getTypeId())) { 184 document.addSearchField(m_solrFields.get(CmsSearchField.FIELD_SEARCH_CHANNEL), "gallery"); 185 } else { 186 document.addSearchField(m_solrFields.get(CmsSearchField.FIELD_SEARCH_CHANNEL), "content"); 187 } 188 189 document = appendFieldsForListSortOptions(document); 190 191 if (resource.getRootPath().startsWith(OpenCms.getSiteManager().getSharedFolder()) 192 || (null != OpenCms.getSiteManager().getSiteRoot(resource.getRootPath()))) { 193 appendSpellFields(document); 194 } 195 196 return document; 197 } 198 199 /** 200 * @see org.opencms.search.fields.CmsSearchFieldConfiguration#appendDates(org.opencms.search.I_CmsSearchDocument, org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.search.extractors.I_CmsExtractionResult, java.util.List, java.util.List) 201 */ 202 @Override 203 protected I_CmsSearchDocument appendDates( 204 I_CmsSearchDocument document, 205 CmsObject cms, 206 CmsResource resource, 207 I_CmsExtractionResult extractionResult, 208 List<CmsProperty> properties, 209 List<CmsProperty> propertiesSearched) { 210 211 document.addDateField(CmsSearchField.FIELD_DATE_CREATED, resource.getDateCreated(), false); 212 document.addDateField(CmsSearchField.FIELD_DATE_LASTMODIFIED, resource.getDateLastModified(), false); 213 document.addDateField(CmsSearchField.FIELD_DATE_CONTENT, resource.getDateContent(), false); 214 document.addDateField(CmsSearchField.FIELD_DATE_RELEASED, resource.getDateReleased(), false); 215 document.addDateField(CmsSearchField.FIELD_DATE_EXPIRED, resource.getDateExpired(), false); 216 217 return document; 218 } 219 220 /** 221 * @see org.opencms.search.fields.CmsSearchFieldConfiguration#appendFieldMapping(org.opencms.search.I_CmsSearchDocument, org.opencms.search.fields.CmsSearchField, org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.search.extractors.I_CmsExtractionResult, java.util.List, java.util.List) 222 */ 223 @Override 224 protected I_CmsSearchDocument appendFieldMapping( 225 I_CmsSearchDocument document, 226 CmsSearchField sfield, 227 CmsObject cms, 228 CmsResource resource, 229 I_CmsExtractionResult extractionResult, 230 List<CmsProperty> properties, 231 List<CmsProperty> propertiesSearched) { 232 233 CmsSolrField field = (CmsSolrField)sfield; 234 try { 235 StringBuffer text = new StringBuffer(); 236 for (I_CmsSearchFieldMapping mapping : field.getMappings()) { 237 // loop over the mappings of the given field 238 if (extractionResult != null) { 239 String mapResult = null; 240 if ((field.getLocale() != null) && mapping.getType().equals(CmsSearchFieldMappingType.CONTENT)) { 241 // this is a localized content field, try to retrieve the localized content extraction 242 mapResult = extractionResult.getContent(field.getLocale()); 243 if (mapResult == null) { 244 // no localized content extracted 245 if (!(CmsResourceTypeXmlContent.isXmlContent(resource) 246 || CmsResourceTypeXmlPage.isXmlPage(resource))) { 247 // the resource is no XML content nor an XML page 248 if ((m_contentLocales != null) && m_contentLocales.contains(field.getLocale())) { 249 // the resource to get the extracted content for has the locale of this field, 250 // so store the extraction content into this field 251 mapResult = extractionResult.getContent(); 252 } 253 } 254 } 255 } else { 256 // this is not a localized content field, just perform the regular mapping 257 mapResult = mapping.getStringValue( 258 cms, 259 resource, 260 extractionResult, 261 properties, 262 propertiesSearched); 263 } 264 if (text.length() > 0) { 265 text.append('\n'); 266 } 267 if (mapResult != null) { 268 text.append(mapResult); 269 } else if (mapping.getDefaultValue() != null) { 270 // no mapping result found, but a default is configured 271 text.append(mapping.getDefaultValue()); 272 } 273 } else if (mapping.getStringValue( 274 cms, 275 resource, 276 extractionResult, 277 properties, 278 propertiesSearched) != null) { 279 String value = mapping.getStringValue( 280 cms, 281 resource, 282 extractionResult, 283 properties, 284 propertiesSearched); 285 if (value != null) { 286 document.addSearchField(field, value); 287 } 288 } 289 } 290 if ((text.length() <= 0) && (field.getDefaultValue() != null)) { 291 text.append(field.getDefaultValue()); 292 } 293 if (text.length() > 0) { 294 document.addSearchField(field, text.toString()); 295 } 296 } catch (Exception e) { 297 // nothing to do just log 298 LOG.error(e); 299 } 300 return document; 301 } 302 303 /** 304 * @see org.opencms.search.fields.CmsSearchFieldConfiguration#appendFieldMappings(org.opencms.search.I_CmsSearchDocument, org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.search.extractors.I_CmsExtractionResult, java.util.List, java.util.List) 305 */ 306 @Override 307 protected I_CmsSearchDocument appendFieldMappings( 308 I_CmsSearchDocument document, 309 CmsObject cms, 310 CmsResource resource, 311 I_CmsExtractionResult extractionResult, 312 List<CmsProperty> properties, 313 List<CmsProperty> propertiesSearched) { 314 315 List<String> systemFields = new ArrayList<String>(); 316 // append field mappings directly stored in the extraction result 317 if (null != extractionResult) { 318 Map<String, String> fieldMappings = extractionResult.getFieldMappings(); 319 for (String fieldName : fieldMappings.keySet()) { 320 String value = fieldMappings.get(fieldName); 321 CmsSolrField f = new CmsSolrField(fieldName, null, null, null); 322 document.addSearchField(f, value); 323 systemFields.add(fieldName); 324 } 325 } 326 327 Set<CmsSearchField> mappedFields = getXSDMappings(cms, resource); 328 if (mappedFields != null) { 329 for (CmsSearchField field : mappedFields) { 330 if (!systemFields.contains(field.getName())) { 331 document = appendFieldMapping( 332 document, 333 field, 334 cms, 335 resource, 336 extractionResult, 337 properties, 338 propertiesSearched); 339 } else { 340 LOG.error( 341 Messages.get().getBundle().key( 342 Messages.LOG_SOLR_ERR_MAPPING_TO_INTERNALLY_USED_FIELD_2, 343 resource.getRootPath(), 344 field.getName())); 345 } 346 } 347 } 348 349 // add field mappings from elements of a container page 350 if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) { 351 document = appendFieldMappingsFromElementsOnThePage(document, cms, resource, systemFields); 352 353 } 354 355 for (CmsSolrField field : m_solrFields.values()) { 356 document = appendFieldMapping( 357 document, 358 field, 359 cms, 360 resource, 361 extractionResult, 362 properties, 363 propertiesSearched); 364 } 365 366 return document; 367 } 368 369 /** 370 * Adds search fields from elements on a container page to a container page's document. 371 * @param document The document for the container page 372 * @param cms The current CmsObject 373 * @param resource The resource of the container page 374 * @param systemFields The list of field names for fields where mappings to should be discarded, since these fields are used system internally. 375 * @return the manipulated document 376 */ 377 protected I_CmsSearchDocument appendFieldMappingsFromElementsOnThePage( 378 I_CmsSearchDocument document, 379 CmsObject cms, 380 CmsResource resource, 381 List<String> systemFields) { 382 383 try { 384 CmsFile file = cms.readFile(resource); 385 CmsXmlContainerPage containerPage = CmsXmlContainerPageFactory.unmarshal(cms, file); 386 CmsContainerPageBean containerBean = containerPage.getContainerPage(cms); 387 if (containerBean != null) { 388 for (CmsContainerElementBean element : containerBean.getElements()) { 389 element.initResource(cms); 390 CmsResource elemResource = element.getResource(); 391 Set<CmsSearchField> mappedFields = getXSDMappingsForPage(cms, elemResource); 392 if (mappedFields != null) { 393 394 for (CmsSearchField field : mappedFields) { 395 if (!systemFields.contains(field.getName())) { 396 document = appendFieldMapping( 397 document, 398 field, 399 cms, 400 elemResource, 401 CmsSolrDocumentXmlContent.extractXmlContent(cms, elemResource, getIndex()), 402 cms.readPropertyObjects(resource, false), 403 cms.readPropertyObjects(resource, true)); 404 } else { 405 LOG.error( 406 Messages.get().getBundle().key( 407 Messages.LOG_SOLR_ERR_MAPPING_TO_INTERNALLY_USED_FIELD_3, 408 elemResource.getRootPath(), 409 field.getName(), 410 resource.getRootPath())); 411 } 412 } 413 } 414 } 415 } 416 } catch (CmsException e) { 417 // Should be thrown if element on the page does not exist anymore - this is possible, but not necessarily an error. 418 // Hence, just notice it in the debug log. 419 if (LOG.isDebugEnabled()) { 420 LOG.debug(e.getLocalizedMessage(), e); 421 } 422 } 423 return document; 424 } 425 426 /** 427 * @see org.opencms.search.fields.CmsSearchFieldConfiguration#appendLocales(org.opencms.search.I_CmsSearchDocument, org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.search.extractors.I_CmsExtractionResult, java.util.List, java.util.List) 428 */ 429 @Override 430 protected I_CmsSearchDocument appendLocales( 431 I_CmsSearchDocument document, 432 CmsObject cms, 433 CmsResource resource, 434 I_CmsExtractionResult extraction, 435 List<CmsProperty> properties, 436 List<CmsProperty> propertiesSearched) { 437 438 // append the resource locales 439 Collection<Locale> resourceLocales = new ArrayList<Locale>(); 440 if ((extraction != null) && (!extraction.getLocales().isEmpty())) { 441 442 CmsResourceManager resMan = OpenCms.getResourceManager(); 443 resourceLocales = extraction.getLocales(); 444 boolean isGroup = false; 445 for (String groupType : Arrays.asList( 446 CmsResourceTypeXmlContainerPage.GROUP_CONTAINER_TYPE_NAME, 447 CmsResourceTypeXmlContainerPage.INHERIT_CONTAINER_TYPE_NAME)) { 448 if (resMan.matchResourceType(groupType, resource.getTypeId())) { 449 isGroup = true; 450 break; 451 } 452 } 453 if (isGroup) { 454 // groups are locale independent, so they have to have *all* locales so they are found for each one 455 m_contentLocales = OpenCms.getLocaleManager().getAvailableLocales(); 456 } else { 457 m_contentLocales = resourceLocales; 458 } 459 } else { 460 // For all other resources add all default locales 461 resourceLocales = OpenCms.getLocaleManager().getDefaultLocales(cms, resource); 462 463 /* 464 * A problem is likely to arise when dealing with multilingual fields: 465 * Only values extracted from XML resources are written into the Solr locale-aware fields (e.g. 466 * "title_<locale>_s"), therefore sorting by them will not work as non-XML (unilingual) resources extract 467 * the information by the resource property facility and will not write to an Solr locale-aware field. 468 * 469 * The following code is used to fix this behavior, at least for "Title". 470 */ 471 472 // Check all passed properties for "Title"... 473 for (final CmsProperty prop : propertiesSearched) { 474 if (prop.getName().equals(CmsPropertyDefinition.PROPERTY_TITLE)) { 475 final String value = prop.getValue(); 476 477 // Write a Solr locale-aware field for every locale the system supports... 478 final List<Locale> availableLocales = OpenCms.getLocaleManager().getAvailableLocales(); 479 for (final Locale locale : availableLocales) { 480 final String lang = locale.getLanguage(); 481 // Don't proceed if a field has already written for this locale. 482 if (!resourceLocales.contains(lang)) { 483 final String effFieldName = CmsSearchFieldConfiguration.getLocaleExtendedName( 484 CmsSearchField.FIELD_TITLE_UNSTORED, 485 locale) + "_s"; 486 487 final CmsSolrField f = new CmsSolrField(effFieldName, null, null, null); 488 document.addSearchField(f, value); 489 } 490 } 491 } 492 } 493 m_contentLocales = getContentLocales(cms, resource, extraction); 494 } 495 496 document.addResourceLocales(resourceLocales); 497 document.addContentLocales(m_contentLocales); 498 499 // append document dependencies if configured 500 if (hasLocaleDependencies()) { 501 CmsDocumentDependency dep = CmsDocumentDependency.load(cms, resource); 502 ((CmsSolrDocument)document).addDocumentDependency(cms, dep); 503 } 504 return document; 505 } 506 507 /** 508 * @see org.opencms.search.fields.CmsSearchFieldConfiguration#appendProperties(org.opencms.search.I_CmsSearchDocument, org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.search.extractors.I_CmsExtractionResult, java.util.List, java.util.List) 509 */ 510 @Override 511 protected I_CmsSearchDocument appendProperties( 512 I_CmsSearchDocument document, 513 CmsObject cms, 514 CmsResource resource, 515 I_CmsExtractionResult extraction, 516 List<CmsProperty> properties, 517 List<CmsProperty> propertiesSearched) { 518 519 for (CmsProperty prop : propertiesSearched) { 520 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(prop.getValue())) { 521 String value = CmsSearchUtil.stripHtmlFromPropertyIfNecessary(prop.getName(), prop.getValue()); 522 document.addSearchField( 523 new CmsSolrField(prop.getName() + CmsSearchField.FIELD_DYNAMIC_PROPERTIES, null, null, null), 524 value); 525 526 // Also write the property using the dynamic field '_s' in order to prevent tokenization 527 // of the property. The resulting field is named '<property>_prop_s'. 528 document.addSearchField( 529 new CmsSolrField(prop.getName() + CmsSearchField.FIELD_DYNAMIC_PROPERTIES + "_s", null, null, null), 530 value); 531 } 532 } 533 534 for (CmsProperty prop : properties) { 535 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(prop.getValue())) { 536 String value = CmsSearchUtil.stripHtmlFromPropertyIfNecessary(prop.getName(), prop.getValue()); 537 document.addSearchField( 538 new CmsSolrField(prop.getName() + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT, null, null, null), 539 value); 540 541 // Also write the property using the dynamic field '_s' in order to prevent tokenization 542 // of the property. The resulting field is named '<property>_prop_nosearch_s'. 543 document.addSearchField( 544 new CmsSolrField( 545 prop.getName() + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT + "_s", 546 null, 547 null, 548 null), 549 value); 550 } 551 } 552 return document; 553 } 554 555 /** 556 * Retrieves the locales for an content, that is whether an XML content nor an XML page.<p> 557 * 558 * Uses following strategy: 559 * <ul> 560 * <li>first by file name</li> 561 * <li>then by detection and</li> 562 * <li>otherwise take the first configured default locale for this resource</li> 563 * </ul> 564 * 565 * @param cms the current CmsObject 566 * @param resource the resource to get the content locales for 567 * @param extraction the extraction result 568 * 569 * @return the determined locales for the given resource 570 */ 571 protected List<Locale> getContentLocales(CmsObject cms, CmsResource resource, I_CmsExtractionResult extraction) { 572 573 // try to detect locale by filename 574 Locale detectedLocale = CmsStringUtil.getLocaleForName(resource.getRootPath()); 575 if (!OpenCms.getLocaleManager().getAvailableLocales(cms, resource).contains(detectedLocale)) { 576 detectedLocale = null; 577 } 578 // try to detect locale by language detector 579 if (getIndex().isLanguageDetection() 580 && (detectedLocale == null) 581 && (extraction != null) 582 && (extraction.getContent() != null)) { 583 detectedLocale = CmsStringUtil.getLocaleForText(extraction.getContent()); 584 } 585 // take the detected locale or use the first configured default locale for this resource 586 List<Locale> result = new ArrayList<Locale>(); 587 if (detectedLocale != null) { 588 // take the found locale 589 result.add(detectedLocale); 590 } else { 591 592 // take all locales set via locale-available or the configured default locales as fall-back for this resource 593 result.addAll(OpenCms.getLocaleManager().getAvailableLocales(cms, resource)); 594 LOG.debug(Messages.get().getBundle().key(Messages.LOG_LANGUAGE_DETECTION_FAILED_1, resource)); 595 } 596 return result; 597 } 598 599 /** 600 * Returns the search field mappings declared within the XSD.<p> 601 * 602 * @param cms the CmsObject 603 * @param resource the resource 604 * 605 * @return the fields to map 606 */ 607 protected Set<CmsSearchField> getXSDMappings(CmsObject cms, CmsResource resource) { 608 609 try { 610 if (CmsResourceTypeXmlContent.isXmlContent(resource)) { 611 I_CmsXmlContentHandler handler = CmsXmlContentDefinition.getContentHandlerForResource(cms, resource); 612 if ((handler != null) && !handler.getSearchFields().isEmpty()) { 613 return handler.getSearchFields(); 614 } 615 } 616 } catch (CmsException e) { 617 LOG.error(e.getMessage(), e); 618 } 619 return null; 620 } 621 622 /** 623 * Returns the search field mappings declared within the XSD that should be applied to the container page.<p> 624 * 625 * @param cms the CmsObject 626 * @param resource the resource 627 * 628 * @return the fields to map 629 */ 630 protected Set<CmsSearchField> getXSDMappingsForPage(CmsObject cms, CmsResource resource) { 631 632 try { 633 if (CmsResourceTypeXmlContent.isXmlContent(resource)) { 634 I_CmsXmlContentHandler handler = CmsXmlContentDefinition.getContentHandlerForResource(cms, resource); 635 if ((handler != null) && !handler.getSearchFieldsForPage().isEmpty()) { 636 return handler.getSearchFieldsForPage(); 637 } 638 } 639 } catch (CmsException e) { 640 LOG.error(e.getMessage(), e); 641 } 642 return null; 643 } 644 645 /** 646 * Adds additional fields to this field configuration.<p> 647 */ 648 private void addAdditionalFields() { 649 650 /* 651 * Add fields from opencms-search.xml (Lucene fields) 652 */ 653 for (CmsSearchField field : getFields()) { 654 if (field instanceof CmsLuceneField) { 655 CmsSolrField newSolrField = new CmsSolrField((CmsLuceneField)field); 656 m_solrFields.put(newSolrField.getName(), newSolrField); 657 } 658 } 659 660 /* 661 * Add the content fields (multiple for contents with more than one locale) 662 */ 663 // add the content_<locale> fields to this configuration 664 CmsSolrField solrField = new CmsSolrField(CmsSearchField.FIELD_CONTENT, null, null, null); 665 solrField.addMapping( 666 new CmsSearchFieldMapping(CmsSearchFieldMappingType.CONTENT, CmsSearchField.FIELD_CONTENT)); 667 m_solrFields.put(solrField.getName(), solrField); 668 for (Locale locale : OpenCms.getLocaleManager().getAvailableLocales()) { 669 solrField = new CmsSolrField( 670 CmsSearchFieldConfiguration.getLocaleExtendedName(CmsSearchField.FIELD_CONTENT, locale), 671 Collections.singletonList(locale.toString() + CmsSearchField.FIELD_EXCERPT), 672 locale, 673 null); 674 solrField.addMapping( 675 new CmsSearchFieldMapping(CmsSearchFieldMappingType.CONTENT, CmsSearchField.FIELD_CONTENT)); 676 m_solrFields.put(solrField.getName(), solrField); 677 } 678 679 /* 680 * Fields filled within appendFields 681 */ 682 CmsSolrField sfield = new CmsSolrField(CmsSearchField.FIELD_MIMETYPE, null, null, null); 683 m_solrFields.put(sfield.getName(), sfield); 684 685 sfield = new CmsSolrField(CmsSearchField.FIELD_FILENAME, null, null, null); 686 m_solrFields.put(sfield.getName(), sfield); 687 688 sfield = new CmsSolrField(CmsSearchField.FIELD_VERSION, null, null, null); 689 m_solrFields.put(sfield.getName(), sfield); 690 691 sfield = new CmsSolrField(CmsSearchField.FIELD_SEARCH_CHANNEL, null, null, null); 692 m_solrFields.put(sfield.getName(), sfield); 693 694 /* 695 * Fields with mapping 696 */ 697 sfield = new CmsSolrField(CmsSearchField.FIELD_STATE, null, null, null); 698 CmsSearchFieldMapping map = new CmsSearchFieldMapping( 699 CmsSearchFieldMappingType.ATTRIBUTE, 700 CmsSearchField.FIELD_STATE); 701 sfield.addMapping(map); 702 m_solrFields.put(sfield.getName(), sfield); 703 704 sfield = new CmsSolrField(CmsSearchField.FIELD_USER_LAST_MODIFIED, null, null, null); 705 map = new CmsSearchFieldMapping(CmsSearchFieldMappingType.ATTRIBUTE, CmsSearchField.FIELD_USER_LAST_MODIFIED); 706 sfield.addMapping(map); 707 m_solrFields.put(sfield.getName(), sfield); 708 709 sfield = new CmsSolrField(CmsSearchField.FIELD_USER_CREATED, null, null, null); 710 map = new CmsSearchFieldMapping(CmsSearchFieldMappingType.ATTRIBUTE, CmsSearchField.FIELD_USER_CREATED); 711 sfield.addMapping(map); 712 m_solrFields.put(sfield.getName(), sfield); 713 714 sfield = new CmsSolrField(CmsSearchField.FIELD_META, null, null, null); 715 map = new CmsSearchFieldMapping(CmsSearchFieldMappingType.PROPERTY, CmsPropertyDefinition.PROPERTY_TITLE); 716 sfield.addMapping(map); 717 map = new CmsSearchFieldMapping(CmsSearchFieldMappingType.PROPERTY, CmsPropertyDefinition.PROPERTY_DESCRIPTION); 718 sfield.addMapping(map); 719 map = new CmsSearchFieldMapping(CmsSearchFieldMappingType.ATTRIBUTE, I_CmsXmlConfiguration.A_NAME); 720 sfield.addMapping(map); 721 m_solrFields.put(sfield.getName(), sfield); 722 723 sfield = new CmsSolrField(CmsSearchField.FIELD_SEARCH_EXCLUDE, null, null, null); 724 map = new CmsSearchFieldMapping( 725 CmsSearchFieldMappingType.PROPERTY_SEARCH, 726 CmsPropertyDefinition.PROPERTY_SEARCH_EXCLUDE); 727 sfield.addMapping(map); 728 m_solrFields.put(sfield.getName(), sfield); 729 730 sfield = new CmsSolrField(CmsSearchField.FIELD_CONTAINER_TYPES, null, null, null); 731 map = new CmsSearchFieldMapping( 732 CmsSearchFieldMappingType.DYNAMIC, 733 "org.opencms.search.galleries.CmsGallerySearchFieldMapping"); 734 map.setDefaultValue("container_types"); 735 sfield.addMapping(map); 736 m_solrFields.put(sfield.getName(), sfield); 737 738 sfield = new CmsSolrField(CmsSearchField.FIELD_ADDITIONAL_INFO, null, null, null); 739 map = new CmsSearchFieldMapping( 740 CmsSearchFieldMappingType.DYNAMIC, 741 "org.opencms.search.galleries.CmsGallerySearchFieldMapping"); 742 map.setDefaultValue("additional_info"); 743 sfield.addMapping(map); 744 m_solrFields.put(sfield.getName(), sfield); 745 } 746 747 /** 748 * Adds multiple fields to the document that are used for the sort options in the list app. 749 * 750 * <p>The fields are: 751 * <ul> 752 * <li>instancedate_dt</li> 753 * <li>disptitle_s</li> 754 * <li>disporder_i</li> 755 * </ul> 756 * and localized versions for each content locale.</p> 757 * 758 * @param document the document to index with all other fields already added. 759 * @return the document extended by the fields used by the list. 760 */ 761 private I_CmsSearchDocument appendFieldsForListSortOptions(I_CmsSearchDocument document) { 762 763 // add non-localized fields 764 // add instance date 765 String fieldName = CmsSearchField.FIELD_INSTANCEDATE + CmsSearchField.FIELD_POSTFIX_DATE; 766 Date instanceDate = document.getFieldValueAsDate(fieldName); 767 if ((null == instanceDate) || (instanceDate.getTime() == 0)) { 768 String instanceDateCopyField = document.getFieldValueAsString( 769 CmsPropertyDefinition.PROPERTY_INSTANCEDATE_COPYFIELD + CmsSearchField.FIELD_DYNAMIC_PROPERTIES); 770 if (null != instanceDateCopyField) { 771 instanceDate = document.getFieldValueAsDate(instanceDateCopyField); 772 } 773 if ((null == instanceDate) || (instanceDate.getTime() == 0)) { 774 instanceDate = document.getFieldValueAsDate(CmsSearchField.FIELD_DATE_RELEASED); 775 } 776 if ((null == instanceDate) || (instanceDate.getTime() == 0)) { 777 instanceDate = document.getFieldValueAsDate(CmsSearchField.FIELD_DATE_LASTMODIFIED); 778 } 779 document.addDateField(fieldName, instanceDate.getTime(), false); 780 } 781 782 // add disp-title field 783 fieldName = CmsSearchField.FIELD_DISPTITLE + CmsSearchField.FIELD_POSTFIX_SORT; 784 String dispTitle = document.getFieldValueAsString(fieldName); 785 if (null == dispTitle) { 786 dispTitle = document.getFieldValueAsString( 787 CmsPropertyDefinition.PROPERTY_TITLE + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT); 788 if (null == dispTitle) { 789 dispTitle = document.getFieldValueAsString(CmsSearchField.FIELD_FILENAME); 790 } 791 document.addSearchField(new CmsSolrField(fieldName, null, null, null), dispTitle); 792 } 793 794 // add disp-order field 795 fieldName = CmsSearchField.FIELD_DISPORDER + CmsSearchField.FIELD_POSTFIX_INT; 796 String dispOrder = document.getFieldValueAsString(fieldName); 797 if (null == dispOrder) { 798 dispOrder = document.getFieldValueAsString( 799 CmsPropertyDefinition.PROPERTY_DISPLAY_ORDER + CmsSearchField.FIELD_DYNAMIC_PROPERTIES); 800 if (null != dispOrder) { 801 try { 802 int o = Integer.parseInt(dispOrder); 803 dispOrder = String.valueOf(o); 804 } catch (NullPointerException | NumberFormatException e) { 805 LOG.warn( 806 "Property " 807 + CmsPropertyDefinition.PROPERTY_DISPLAY_ORDER 808 + " contains not a valid integer number."); 809 dispOrder = "0"; 810 } 811 } else { 812 dispOrder = "0"; 813 } 814 document.addSearchField(new CmsSolrField(fieldName, null, null, null), dispOrder); 815 } 816 817 // add localized fields 818 for (String locale : document.getMultivaluedFieldAsStringList(CmsSearchField.FIELD_CONTENT_LOCALES)) { 819 // instance date 820 fieldName = CmsSearchField.FIELD_INSTANCEDATE + "_" + locale + CmsSearchField.FIELD_POSTFIX_DATE; 821 Date presetInstanceDate = document.getFieldValueAsDate(fieldName); 822 if ((null == presetInstanceDate) || (presetInstanceDate.getTime() == 0)) { 823 document.addDateField(fieldName, instanceDate.getTime(), false); 824 } 825 // disp-title field for title display and sorting 826 fieldName = CmsSearchField.FIELD_DISPTITLE + "_" + locale + CmsSearchField.FIELD_POSTFIX_SORT; 827 if (null == document.getFieldValueAsString(fieldName)) { 828 String localizedTitle = document.getFieldValueAsString( 829 CmsPropertyDefinition.PROPERTY_TITLE 830 + "_" 831 + locale 832 + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT); 833 document.addSearchField( 834 new CmsSolrField(fieldName, null, null, null), 835 null == localizedTitle ? dispTitle : localizedTitle); 836 } 837 // disp-order field 838 fieldName = CmsSearchField.FIELD_DISPORDER + "_" + locale + CmsSearchField.FIELD_POSTFIX_INT; 839 if (null == document.getFieldValueAsString(fieldName)) { 840 String localizedOrder = document.getFieldValueAsString( 841 CmsPropertyDefinition.PROPERTY_DISPLAY_ORDER 842 + "_" 843 + locale 844 + CmsSearchField.FIELD_DYNAMIC_PROPERTIES); 845 if (null != localizedOrder) { 846 try { 847 int o = Integer.parseInt(localizedOrder); 848 localizedOrder = String.valueOf(o); 849 } catch (NullPointerException | NumberFormatException e) { 850 LOG.warn( 851 "Property " 852 + CmsPropertyDefinition.PROPERTY_DISPLAY_ORDER 853 + "_" 854 + locale 855 + " contains not a valid integer number."); 856 } 857 } 858 document.addSearchField( 859 new CmsSolrField(fieldName, null, null, null), 860 null == localizedOrder ? dispOrder : localizedOrder); 861 } 862 } 863 864 return document; 865 } 866 867 /** 868 * Copy the content and the title property of the document to a spell field / a language specific spell field. 869 * @param document the document that gets extended by the spell fields. 870 */ 871 private void appendSpellFields(I_CmsSearchDocument document) { 872 873 /* 874 * Add the content fields (multiple for contents with more than one locale) 875 */ 876 // add the content_<locale> fields to this configuration 877 String title = document.getFieldValueAsString( 878 CmsPropertyDefinition.PROPERTY_TITLE + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT); 879 document.addSearchField( 880 new CmsSolrField(CmsSearchField.FIELD_SPELL, null, null, null), 881 document.getFieldValueAsString(CmsSearchField.FIELD_CONTENT) + "\n" + title); 882 for (Locale locale : OpenCms.getLocaleManager().getAvailableLocales()) { 883 document.addSearchField( 884 new CmsSolrField(locale + "_" + CmsSearchField.FIELD_SPELL, null, locale, null), 885 document.getFieldValueAsString( 886 CmsSearchFieldConfiguration.getLocaleExtendedName(CmsSearchField.FIELD_CONTENT, locale)) 887 + "\n" 888 + title); 889 } 890 } 891 892 /** 893 * Returns <code>true</code> if at least one of the index sources uses a VFS indexer that is able 894 * to index locale dependent resources.<p> 895 * 896 * TODO This should be improved somehow 897 * 898 * @return <code>true</code> if this field configuration should resolve locale dependencies 899 */ 900 private boolean hasLocaleDependencies() { 901 902 for (CmsSearchIndexSource source : getIndex().getSources()) { 903 if (source.getIndexer().isLocaleDependenciesEnable()) { 904 return true; 905 } 906 } 907 return false; 908 } 909}