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