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.acacia.shared.I_CmsSerialDateValue;
035import org.opencms.ade.containerpage.CmsDetailOnlyContainerUtil;
036import org.opencms.file.CmsFile;
037import org.opencms.file.CmsObject;
038import org.opencms.file.CmsPropertyDefinition;
039import org.opencms.file.CmsResource;
040import org.opencms.file.types.CmsResourceTypeXmlContent;
041import org.opencms.i18n.CmsLocaleManager;
042import org.opencms.main.CmsException;
043import org.opencms.main.CmsLog;
044import org.opencms.main.OpenCms;
045import org.opencms.search.CmsIndexException;
046import org.opencms.search.CmsSearchUtil;
047import org.opencms.search.I_CmsSearchIndex;
048import org.opencms.search.documents.A_CmsVfsDocument;
049import org.opencms.search.documents.CmsIndexNoContentException;
050import org.opencms.search.documents.Messages;
051import org.opencms.search.extractors.CmsExtractionResult;
052import org.opencms.search.extractors.I_CmsExtractionResult;
053import org.opencms.search.fields.CmsSearchField;
054import org.opencms.search.fields.CmsSearchFieldConfiguration;
055import org.opencms.search.galleries.CmsGalleryNameMacroResolver;
056import org.opencms.util.CmsStringUtil;
057import org.opencms.util.CmsUUID;
058import org.opencms.widgets.serialdate.CmsSerialDateBeanFactory;
059import org.opencms.widgets.serialdate.CmsSerialDateValue;
060import org.opencms.widgets.serialdate.I_CmsSerialDateBean;
061import org.opencms.xml.A_CmsXmlDocument;
062import org.opencms.xml.CmsXmlContentDefinition;
063import org.opencms.xml.CmsXmlUtils;
064import org.opencms.xml.content.CmsXmlContent;
065import org.opencms.xml.content.CmsXmlContentFactory;
066import org.opencms.xml.content.I_CmsXmlContentHandler;
067import org.opencms.xml.types.CmsXmlDateTimeValue;
068import org.opencms.xml.types.CmsXmlHtmlValue;
069import org.opencms.xml.types.CmsXmlNestedContentDefinition;
070import org.opencms.xml.types.CmsXmlSerialDateValue;
071import org.opencms.xml.types.I_CmsXmlContentValue;
072import org.opencms.xml.types.I_CmsXmlSchemaType;
073
074import java.util.ArrayList;
075import java.util.Collections;
076import java.util.HashMap;
077import java.util.HashSet;
078import java.util.LinkedHashMap;
079import java.util.List;
080import java.util.Locale;
081import java.util.Map;
082import java.util.Set;
083import java.util.function.Consumer;
084
085import org.apache.commons.logging.Log;
086
087import com.google.common.collect.Sets;
088
089/**
090 * Special document text extraction factory for Solr index.<p>
091 *
092 * @since 8.5.0
093 */
094public class CmsSolrDocumentXmlContent extends A_CmsVfsDocument {
095
096    /**
097     * The gallery name is determined by resolving the macros in a string which can either come from a field mapped
098     * to the gallery name, or the title, or from default values for those fields. This class is used to select the
099     * value to use and performs the macro substitution.
100     */
101    private static class GalleryNameChooser {
102
103        /** CMS context for this instance. */
104        private CmsObject m_cms;
105
106        /** Current XML content. */
107        private A_CmsXmlDocument m_content;
108
109        /** Default value of field mapped to gallery name. */
110        private String m_defaultGalleryNameValue;
111
112        /** Default value of field mapped to title. */
113        private String m_defaultTitleValue;
114
115        /** Current locale. */
116        private Locale m_locale;
117
118        /** Content value mapped to Description property. */
119        private String m_mappedDescriptionValue;
120
121        /** Content value mapped to gallery description. */
122        private String m_mappedGalleryDescriptionValue;
123
124        /** Content value mapped to gallery name. */
125        private String m_mappedGalleryNameValue;
126
127        /** Content value mapped to title. */
128        private String m_mappedTitleValue;
129
130        /**
131         * Creates a new instance.<p>
132         *
133         * @param cms the CMS context
134         * @param content the XML content
135         * @param locale the locale in the XML content
136         */
137        public GalleryNameChooser(CmsObject cms, A_CmsXmlDocument content, Locale locale) {
138
139            m_cms = cms;
140            m_content = content;
141            m_locale = locale;
142        }
143
144        /**
145         * Selects the description displayed in the gallery.<p>
146         *
147         * This method assumes that all the available values have been set via the setters of this class.
148         *
149         * @return the description
150         *
151         * @throws CmsException of something goes wrong
152         */
153        public String getDescription() throws CmsException {
154
155            return getDescription(m_locale);
156        }
157
158        /**
159            * Selects the description displayed in the gallery.<p>
160            *
161            * This method assumes that all the available values have been set via the setters of this class.
162            *
163            * @param locale the locale to get the description in
164            *
165            * @return the description
166            *
167            * @throws CmsException of something goes wrong
168            */
169        public String getDescription(Locale locale) throws CmsException {
170
171            String result = null;
172            for (String resultCandidateWithMacros : new String[] {
173                m_mappedGalleryDescriptionValue,
174                m_mappedDescriptionValue}) {
175                if (!CmsStringUtil.isEmptyOrWhitespaceOnly(resultCandidateWithMacros)) {
176                    CmsGalleryNameMacroResolver resolver = new CmsGalleryNameMacroResolver(m_cms, m_content, locale);
177                    result = resolver.resolveMacros(resultCandidateWithMacros);
178                    return result;
179                }
180            }
181            result = m_cms.readPropertyObject(
182                m_content.getFile(),
183                CmsPropertyDefinition.PROPERTY_DESCRIPTION,
184                false).getValue();
185            return result;
186        }
187
188        /**
189         * Selects the gallery name.<p>
190         *
191         * This method assumes that all the available values have been set via the setters of this class.
192         *
193         * @return the gallery name
194         *
195         * @throws CmsException of something goes wrong
196         */
197        public String getGalleryName() throws CmsException {
198
199            return getGalleryName(m_locale);
200        }
201
202        /**
203        * Selects the gallery name.<p>
204        *
205        * This method assumes that all the available values have been set via the setters of this class.
206        *
207        * @param locale the locale to get the gallery name in
208        *
209        * @return the gallery name
210        *
211        * @throws CmsException of something goes wrong
212        */
213        public String getGalleryName(Locale locale) throws CmsException {
214
215            String result = null;
216            for (String resultCandidateWithMacros : new String[] {
217                // Prioritize gallery name over title, and actual content values over defaults
218                m_mappedGalleryNameValue,
219                m_defaultGalleryNameValue,
220                m_mappedTitleValue,
221                m_defaultTitleValue}) {
222                if (!CmsStringUtil.isEmptyOrWhitespaceOnly(resultCandidateWithMacros)) {
223                    CmsGalleryNameMacroResolver resolver = new CmsGalleryNameMacroResolver(m_cms, m_content, locale);
224                    result = resolver.resolveMacros(resultCandidateWithMacros);
225                    return result;
226                }
227            }
228            result = m_cms.readPropertyObject(
229                m_content.getFile(),
230                CmsPropertyDefinition.PROPERTY_TITLE,
231                false).getValue();
232            return result;
233        }
234
235        /**
236         * Sets the defaultGalleryNameValue.<p>
237         *
238         * @param defaultGalleryNameValue the defaultGalleryNameValue to set
239         */
240        public void setDefaultGalleryNameValue(String defaultGalleryNameValue) {
241
242            m_defaultGalleryNameValue = defaultGalleryNameValue;
243        }
244
245        /**
246         * Sets the defaultTitleValue.<p>
247         *
248         * @param defaultTitleValue the defaultTitleValue to set
249         */
250        public void setDefaultTitleValue(String defaultTitleValue) {
251
252            m_defaultTitleValue = defaultTitleValue;
253        }
254
255        /**
256         * Sets the mapped description value.<p>
257         *
258         * @param mappedDescriptionValue the mappedDescriptionValue to set
259         */
260        public void setMappedDescriptionValue(String mappedDescriptionValue) {
261
262            m_mappedDescriptionValue = mappedDescriptionValue;
263        }
264
265        /**
266         * Sets the name from a value mapped via 'galleryDescription'.
267         *
268         * @param mappedGalleryDescriptionValue the value that has been mapped
269         */
270        public void setMappedGalleryDescriptionValue(String mappedGalleryDescriptionValue) {
271
272            m_mappedGalleryDescriptionValue = mappedGalleryDescriptionValue;
273        }
274
275        /**
276         * Sets the mappedGalleryNameValue.<p>
277         *
278         * @param mappedGalleryNameValue the mappedGalleryNameValue to set
279         */
280        public void setMappedGalleryNameValue(String mappedGalleryNameValue) {
281
282            m_mappedGalleryNameValue = mappedGalleryNameValue;
283        }
284
285        /**
286         * Sets the mappedTitleValue.<p>
287         *
288         * @param mappedTitleValue the mappedTitleValue to set
289         */
290        public void setMappedTitleValue(String mappedTitleValue) {
291
292            m_mappedTitleValue = mappedTitleValue;
293        }
294    }
295
296    /** Mapping name used to indicate that the value should be used for the gallery description. */
297    public static final String MAPPING_GALLERY_DESCRIPTION = "galleryDescription";
298
299    /** Mapping name used to indicate that the value should be used for the gallery name. */
300    public static final String MAPPING_GALLERY_NAME = "galleryName";
301
302    /** The log object for this class. */
303    private static final Log LOG = CmsLog.getLog(CmsSolrDocumentXmlContent.class);
304
305    /**
306     * Public constructor.<p>
307     *
308     * @param name the name for the document type
309     */
310    public CmsSolrDocumentXmlContent(String name) {
311
312        super(name);
313    }
314
315    /**
316     * Collects a list of all possible XPaths for a content definition.<p>
317     *
318     * @param cms the CMS context to use
319     * @param def the content definition
320     * @param path the path of the given content definition
321     * @param result the set used to collect the XPaths
322     */
323    public static void collectSchemaXpathsForSimpleValues(
324        CmsObject cms,
325        CmsXmlContentDefinition def,
326        String path,
327        Set<String> result) {
328
329        List<I_CmsXmlSchemaType> nestedTypes = def.getTypeSequence();
330        for (I_CmsXmlSchemaType nestedType : nestedTypes) {
331            String subPath = path + "/" + nestedType.getName();
332            if (nestedType instanceof CmsXmlNestedContentDefinition) {
333                CmsXmlContentDefinition nestedDef = ((CmsXmlNestedContentDefinition)nestedType).getNestedContentDefinition();
334                collectSchemaXpathsForSimpleValues(cms, nestedDef, subPath, result);
335            } else {
336                result.add(subPath);
337            }
338        }
339    }
340
341    /**
342     * Extracts the content of a single XML content resource.<p>
343     *
344     * @param cms the cms context
345     * @param resource the resource
346     * @param index the used index
347     *
348     * @return the extraction result
349     *
350     * @throws CmsException in case reading or unmarshalling the content fails
351     */
352    public static CmsExtractionResult extractXmlContent(CmsObject cms, CmsResource resource, I_CmsSearchIndex index)
353    throws CmsException {
354
355        return extractXmlContent(cms, resource, index, null);
356    }
357
358    /**
359     * Extracts the content of a single XML content resource.<p>
360     *
361     * @param cms the cms context
362     * @param resource the resource
363     * @param index the used index
364     * @param forceLocale if set, only the content values for the given locale will be extracted
365     *
366     * @return the extraction result
367     *
368     * @throws CmsException in case reading or unmarshalling the content fails
369     */
370    public static CmsExtractionResult extractXmlContent(
371        CmsObject cms,
372        CmsResource resource,
373        I_CmsSearchIndex index,
374        Locale forceLocale)
375    throws CmsException {
376
377        return extractXmlContent(cms, resource, index, forceLocale, null);
378    }
379
380    /**
381     * Extracts the content of a single XML content resource.<p>
382     *
383     * @param cms the cms context
384     * @param resource the resource
385     * @param index the used index
386     * @param forceLocale if set, only the content values for the given locale will be extracted
387     * @param alreadyExtracted keeps track of ids of contents which have already been extracted
388     *
389     * @return the extraction result
390     *
391     * @throws CmsException in case reading or unmarshalling the content fails
392     */
393    public static CmsExtractionResult extractXmlContent(
394        CmsObject cms,
395        CmsResource resource,
396        I_CmsSearchIndex index,
397        Locale forceLocale,
398        Set<CmsUUID> alreadyExtracted)
399    throws CmsException {
400
401        return extractXmlContent(
402            cms,
403            resource,
404            index,
405            forceLocale,
406            alreadyExtracted,
407            content -> {/*do nothing with the content*/});
408
409    }
410
411    /**
412     * Extracts the content of a single XML content resource.<p>
413     *
414     * @param cms the cms context
415     * @param resource the resource
416     * @param index the used index
417     * @param forceLocale if set, only the content values for the given locale will be extracted
418     * @param alreadyExtracted keeps track of ids of contents which have already been extracted
419     * @param contentConsumer gets called with the unmarshalled content object
420     *
421     * @return the extraction result
422     *
423     * @throws CmsException in case reading or unmarshalling the content fails
424     */
425    public static CmsExtractionResult extractXmlContent(
426        CmsObject cms,
427        CmsResource resource,
428        I_CmsSearchIndex index,
429        Locale forceLocale,
430        Set<CmsUUID> alreadyExtracted,
431        Consumer<A_CmsXmlDocument> contentConsumer)
432    throws CmsException {
433
434        if (null == alreadyExtracted) {
435            alreadyExtracted = Collections.emptySet();
436        }
437        // un-marshal the content
438        CmsFile file = cms.readFile(resource);
439        if (file.getLength() <= 0) {
440            throw new CmsIndexNoContentException(
441                Messages.get().container(Messages.ERR_NO_CONTENT_1, resource.getRootPath()));
442        }
443        A_CmsXmlDocument xmlContent = CmsXmlContentFactory.unmarshal(cms, file);
444        if (contentConsumer != null) {
445            contentConsumer.accept(xmlContent);
446        }
447
448        // initialize some variables
449        Map<Locale, LinkedHashMap<String, String>> items = new HashMap<Locale, LinkedHashMap<String, String>>();
450        Map<String, String> fieldMappings = new HashMap<String, String>();
451        List<Locale> contentLocales = forceLocale != null
452        ? Collections.singletonList(forceLocale)
453        : xmlContent.getLocales();
454        Locale resourceLocale = index.getLocaleForResource(cms, resource, contentLocales);
455
456        LinkedHashMap<String, String> localeItems = null;
457        GalleryNameChooser galleryNameChooser = null;
458        // loop over the locales of the content
459        for (Locale locale : contentLocales) {
460            galleryNameChooser = new GalleryNameChooser(cms, xmlContent, locale);
461            localeItems = new LinkedHashMap<String, String>();
462            StringBuffer textContent = new StringBuffer();
463            // store the locales of the content as space separated field
464            // loop over the available element paths of the current content locale
465            List<String> paths = xmlContent.getNames(locale);
466            for (String xpath : paths) {
467
468                // try to get the value extraction for the current element path
469                String extracted = null;
470                I_CmsXmlContentValue value = xmlContent.getValue(xpath, locale);
471                try {
472                    //the new DatePointField.createField dose not support milliseconds
473                    if (value instanceof CmsXmlDateTimeValue) {
474                        extracted = CmsSearchUtil.getDateAsIso8601(((CmsXmlDateTimeValue)value).getDateTimeValue());
475                    } else {
476                        extracted = value.getPlainText(cms);
477                        if (CmsStringUtil.isEmptyOrWhitespaceOnly(extracted)
478                            && value.isSimpleType()
479                            && !(value instanceof CmsXmlHtmlValue)) {
480                            // no text value for simple type, so take the string value as item
481                            // prevent this for elements of type "OpenCmsHtml", since this causes problematic values
482                            // being indexed, e.g., <iframe ...></iframe>
483                            // TODO: Why is this special handling needed at all???
484                            extracted = value.getStringValue(cms);
485                        }
486                    }
487                } catch (Exception e) {
488                    // it can happen that a exception is thrown while extracting a single value
489                    LOG.warn(Messages.get().container(Messages.LOG_EXTRACT_VALUE_2, xpath, resource), e);
490                }
491
492                // put the extraction to the items and to the textual content
493                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(extracted)) {
494                    localeItems.put(xpath, extracted);
495                }
496                switch (xmlContent.getHandler().getSearchContentType(value)) {
497                    case TRUE:
498                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(extracted)) {
499                            textContent.append(extracted);
500                            textContent.append('\n');
501                        }
502                        break;
503                    case CONTENT:
504                        // TODO: Potentially extend to allow for indexing of non-xml-contents as well.
505                        String potentialLinkValue = value.getStringValue(cms);
506                        try {
507                            if ((null != potentialLinkValue)
508                                && !potentialLinkValue.isEmpty()
509                                && cms.existsResource(potentialLinkValue)) {
510                                CmsResource linkedRes = cms.readResource(potentialLinkValue);
511                                if (CmsResourceTypeXmlContent.isXmlContent(linkedRes)
512                                    && !alreadyExtracted.contains(linkedRes.getStructureId())) {
513                                    Set<CmsUUID> newAlreadyExtracted = new HashSet<>(alreadyExtracted);
514                                    newAlreadyExtracted.add(resource.getStructureId());
515                                    I_CmsExtractionResult exRes = CmsSolrDocumentXmlContent.extractXmlContent(
516                                        cms,
517                                        linkedRes,
518                                        index,
519                                        locale,
520                                        newAlreadyExtracted);
521                                    String exContent = exRes.getContent(locale);
522                                    if ((exContent != null) && !exContent.trim().isEmpty()) {
523                                        textContent.append(exContent.trim());
524                                        textContent.append('\n');
525                                        break; // Success - we break here to not repeatedly programm a warning.
526                                    }
527                                }
528                            }
529                            if (LOG.isInfoEnabled()) {
530                                LOG.info(
531                                    "When indexing resource "
532                                        + resource.getRootPath()
533                                        + ", the elements value "
534                                        + value.getPath()
535                                        + " in locale "
536                                        + locale
537                                        + " does not contain a link to an XML content. Hence, the linked element's content is not added to the content indexed for the resource itself.");
538                            }
539                        } catch (Throwable t) {
540                            LOG.error(
541                                "Failed to add content of resource (site path) "
542                                    + potentialLinkValue
543                                    + " to content of resource (root path) "
544                                    + resource.getRootPath()
545                                    + " when indexing the resource for locale "
546                                    + locale
547                                    + ". Skipping this content part.",
548                                t);
549                        }
550                        break;
551                    default:
552                        // we do not index the content element for the content field.
553                        break;
554                }
555
556                List<String> mappings = xmlContent.getHandler().getMappings(value.getPath());
557                if (mappings.size() > 0) {
558                    // mappings are defined, lets check if we have mappings that interest us
559                    for (String mapping : mappings) {
560                        if (mapping.startsWith(I_CmsXmlContentHandler.MAPTO_PROPERTY)) {
561                            // this is a property mapping
562                            String propertyName = mapping.substring(mapping.lastIndexOf(':') + 1);
563                            if (CmsPropertyDefinition.PROPERTY_TITLE.equals(propertyName)
564                                || CmsPropertyDefinition.PROPERTY_DESCRIPTION.equals(propertyName)) {
565
566                                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(extracted)) {
567                                    if (CmsPropertyDefinition.PROPERTY_TITLE.equals(propertyName)) {
568                                        galleryNameChooser.setMappedTitleValue(extracted);
569                                    } else {
570                                        // if field is not title, it must be description
571                                        galleryNameChooser.setMappedDescriptionValue(extracted);
572                                    }
573                                }
574                            }
575                        } else if (mapping.equals(MAPPING_GALLERY_NAME)) {
576                            galleryNameChooser.setMappedGalleryNameValue(value.getPlainText(cms));
577                        } else if (mapping.equals(MAPPING_GALLERY_DESCRIPTION)) {
578                            galleryNameChooser.setMappedGalleryDescriptionValue(value.getPlainText(cms));
579                        }
580                    }
581                }
582                if (value instanceof CmsXmlSerialDateValue) {
583                    if ((null != extracted) && !extracted.isEmpty()) {
584                        I_CmsSerialDateValue serialDateValue = new CmsSerialDateValue(extracted);
585                        I_CmsSerialDateBean serialDateBean = CmsSerialDateBeanFactory.createSerialDateBean(
586                            serialDateValue);
587                        if (null != serialDateBean) {
588                            StringBuffer values = new StringBuffer();
589                            StringBuffer endValues = new StringBuffer();
590                            StringBuffer currentTillValues = new StringBuffer();
591                            for (Long eventDate : serialDateBean.getDatesAsLong()) {
592                                values.append("\n").append(eventDate.toString());
593                                long endDate = null != serialDateBean.getEventDuration()
594                                ? eventDate.longValue() + serialDateBean.getEventDuration().longValue()
595                                : eventDate.longValue();
596                                endValues.append("\n").append(Long.toString(endDate));
597                                // Special treatment for events that end at 00:00:
598                                // To not show them at the day after they ended, one millisecond is removed from the end time
599                                // for the "currenttill"-time
600                                currentTillValues.append("\n").append(
601                                    serialDateValue.isCurrentTillEnd()
602                                    ? Long.valueOf(
603                                        serialDateValue.endsAtMidNight() && (endDate > eventDate.longValue())
604                                        ? endDate - 1L
605                                        : endDate)
606                                    : eventDate);
607                            }
608                            fieldMappings.put(CmsSearchField.FIELD_SERIESDATES, values.substring(1));
609                            fieldMappings.put(CmsSearchField.FIELD_SERIESDATES_END, endValues.substring(1));
610                            fieldMappings.put(
611                                CmsSearchField.FIELD_SERIESDATES_CURRENT_TILL,
612                                currentTillValues.substring(1));
613                            fieldMappings.put(
614                                CmsSearchField.FIELD_SERIESDATES_TYPE,
615                                serialDateValue.getDateType().toString());
616                        } else {
617                            LOG.warn(
618                                "Serial date value \""
619                                    + value.getStringValue(cms)
620                                    + "\" at element \""
621                                    + value.getPath()
622                                    + "\" is invalid. No dates are indexed for resource \""
623                                    + resource.getRootPath()
624                                    + "\".");
625                        }
626                    }
627                }
628            }
629
630            Set<String> xpaths = Sets.newHashSet();
631            collectSchemaXpathsForSimpleValues(cms, xmlContent.getContentDefinition(), "", xpaths);
632            for (String xpath : xpaths) {
633                // mappings always are stored with indexes, so we add them to the xpath
634                List<String> mappings = xmlContent.getHandler().getMappings(CmsXmlUtils.createXpath(xpath, 1));
635                for (String mapping : mappings) {
636
637                    if (mapping.equals(MAPPING_GALLERY_NAME)
638                        || mapping.equals(
639                            I_CmsXmlContentHandler.MAPTO_PROPERTY + CmsPropertyDefinition.PROPERTY_TITLE)) {
640                        String defaultValue = xmlContent.getHandler().getDefault(
641                            cms,
642                            xmlContent.getFile(),
643                            null,
644                            xpath,
645                            locale);
646                        if (mapping.equals(MAPPING_GALLERY_NAME)) {
647                            galleryNameChooser.setDefaultGalleryNameValue(defaultValue);
648                        } else {
649                            galleryNameChooser.setDefaultTitleValue(defaultValue);
650                        }
651                    }
652                }
653            }
654
655            final String galleryTitleFieldKey = CmsSearchFieldConfiguration.getLocaleExtendedName(
656                CmsSearchField.FIELD_TITLE_UNSTORED,
657                locale) + "_s";
658            final String galleryNameValue = galleryNameChooser.getGalleryName();
659            fieldMappings.put(galleryTitleFieldKey, galleryNameValue);
660            fieldMappings.put(
661                CmsSearchFieldConfiguration.getLocaleExtendedName(CmsSearchField.FIELD_DESCRIPTION, locale) + "_s",
662                galleryNameChooser.getDescription());
663
664            // handle the textual content
665            if (textContent.length() > 0) {
666                // add the textual content with a localized key to the items
667                //String key = CmsSearchFieldConfiguration.getLocaleExtendedName(CmsSearchField.FIELD_CONTENT, locale);
668                //items.put(key, textContent.toString());
669                // use the default locale of this resource as general text content for the extraction result
670                localeItems.put(I_CmsExtractionResult.ITEM_CONTENT, textContent.toString());
671            }
672            items.put(locale, localeItems);
673        }
674        // if the content is locale independent, it should have only one content locale, but that should be indexed for all available locales.
675        // TODO: One could think of different indexing behavior, i.e., index only for getDefaultLocales(cms,resource)
676        //       But using getAvailableLocales(cms,resource) does not work, because locale-available is set to "en" for all that content.
677        if ((xmlContent instanceof CmsXmlContent) && ((CmsXmlContent)xmlContent).isLocaleIndependent()) {
678            if (forceLocale != null) {
679                items.put(forceLocale, localeItems);
680            } else {
681                for (Locale l : OpenCms.getLocaleManager().getAvailableLocales()) {
682                    items.put(l, localeItems);
683                    if (null != galleryNameChooser) {
684                        final String galleryTitleFieldKey = CmsSearchFieldConfiguration.getLocaleExtendedName(
685                            CmsSearchField.FIELD_TITLE_UNSTORED,
686                            l) + "_s";
687                        fieldMappings.put(galleryTitleFieldKey, galleryNameChooser.getGalleryName(l));
688                        fieldMappings.put(
689                            CmsSearchFieldConfiguration.getLocaleExtendedName(CmsSearchField.FIELD_DESCRIPTION, l)
690                                + "_s",
691                            galleryNameChooser.getDescription(l));
692                    }
693                }
694            }
695        }
696        // add the locales that have been indexed for this document as item and return the extraction result
697        // fieldMappings.put(CmsSearchField.FIELD_RESOURCE_LOCALES, locales.toString().trim());
698        return new CmsExtractionResult(resourceLocale, items, fieldMappings);
699
700    }
701
702    /**
703     * @see org.opencms.search.documents.CmsDocumentXmlContent#extractContent(org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.search.I_CmsSearchIndex)
704     */
705    @Override
706    public I_CmsExtractionResult extractContent(CmsObject cms, CmsResource resource, I_CmsSearchIndex index)
707    throws CmsException {
708
709        logContentExtraction(resource, index);
710
711        try {
712            I_CmsExtractionResult result = null;
713            List<I_CmsExtractionResult> ex = new ArrayList<I_CmsExtractionResult>();
714            for (CmsResource detailContainers : CmsDetailOnlyContainerUtil.getDetailOnlyResources(cms, resource)) {
715                CmsSolrDocumentContainerPage containerpageExtractor = new CmsSolrDocumentContainerPage("");
716                String localeTemp = detailContainers.getRootPath();
717                localeTemp = CmsResource.getParentFolder(localeTemp);
718                localeTemp = CmsResource.getName(localeTemp);
719                localeTemp = localeTemp.substring(0, localeTemp.length() - 1);
720                Locale locale = CmsLocaleManager.getLocale(localeTemp);
721                if (CmsDetailOnlyContainerUtil.useSingleLocaleDetailContainers(
722                    OpenCms.getSiteManager().getSiteRoot(resource.getRootPath()))
723                    && locale.equals(CmsLocaleManager.getDefaultLocale())) {
724                    // in case of single locale detail containers do not force the locale
725                    locale = null;
726                }
727                I_CmsExtractionResult containersExtractionResult = containerpageExtractor.extractContent(
728                    cms,
729                    detailContainers,
730                    index,
731                    locale);
732                // only use the locales of the resource itself, not the ones of the detail containers page
733                containersExtractionResult.getContentItems().remove(CmsSearchField.FIELD_RESOURCE_LOCALES);
734
735                ex.add(containersExtractionResult);
736            }
737            result = extractXmlContent(cms, resource, index);
738            result = result.merge(ex);
739            return result;
740
741        } catch (Throwable t) {
742            throw new CmsIndexException(Messages.get().container(Messages.ERR_TEXT_EXTRACTION_1, resource), t);
743        }
744    }
745
746    /**
747     * Solr index content is stored in multiple languages, so the result is NOT locale dependent.<p>
748     *
749     * @see org.opencms.search.documents.I_CmsDocumentFactory#isLocaleDependend()
750     */
751    public boolean isLocaleDependend() {
752
753        return false;
754    }
755
756    /**
757     * @see org.opencms.search.documents.I_CmsDocumentFactory#isUsingCache()
758     */
759    @Override
760    public boolean isOnlyDependentOnContent() {
761
762        return false;
763    }
764
765    /**
766     * @see org.opencms.search.documents.I_CmsDocumentFactory#isUsingCache()
767     */
768    public boolean isUsingCache() {
769
770        return false;
771    }
772}