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}