001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.configuration;
029
030import org.opencms.ade.configuration.CmsADEConfigDataInternal.AttributeValue;
031import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceMeta;
032import org.opencms.ade.configuration.formatters.CmsFormatterBeanParser;
033import org.opencms.ade.configuration.formatters.CmsFormatterChangeSet;
034import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCacheState;
035import org.opencms.ade.configuration.formatters.CmsFormatterIndex;
036import org.opencms.ade.configuration.plugins.CmsSitePlugin;
037import org.opencms.ade.containerpage.shared.CmsContainer;
038import org.opencms.ade.containerpage.shared.CmsContainerElement;
039import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
040import org.opencms.ade.detailpage.CmsDetailPageInfo;
041import org.opencms.ade.galleries.CmsAddContentRestriction;
042import org.opencms.file.CmsObject;
043import org.opencms.file.CmsResource;
044import org.opencms.file.CmsResourceFilter;
045import org.opencms.file.types.CmsResourceTypeFolder;
046import org.opencms.file.types.CmsResourceTypeFunctionConfig;
047import org.opencms.file.types.CmsResourceTypeXmlContent;
048import org.opencms.file.types.I_CmsResourceType;
049import org.opencms.gwt.CmsIconUtil;
050import org.opencms.gwt.shared.CmsGwtConstants;
051import org.opencms.jsp.util.CmsFunctionRenderer;
052import org.opencms.loader.CmsLoaderException;
053import org.opencms.main.CmsException;
054import org.opencms.main.CmsLog;
055import org.opencms.main.OpenCms;
056import org.opencms.main.OpenCmsServlet;
057import org.opencms.util.CmsFileUtil;
058import org.opencms.util.CmsStringUtil;
059import org.opencms.util.CmsUUID;
060import org.opencms.workplace.editors.directedit.CmsAdvancedDirectEditProvider.SitemapDirectEditPermissions;
061import org.opencms.xml.CmsLinkFinisher;
062import org.opencms.xml.CmsXmlContentDefinition;
063import org.opencms.xml.containerpage.CmsFormatterConfiguration;
064import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler;
065import org.opencms.xml.containerpage.I_CmsFormatterBean;
066import org.opencms.xml.content.CmsXmlContentFactory;
067import org.opencms.xml.content.CmsXmlContentProperty;
068
069import java.util.ArrayList;
070import java.util.Arrays;
071import java.util.Collection;
072import java.util.Collections;
073import java.util.HashMap;
074import java.util.HashSet;
075import java.util.Iterator;
076import java.util.LinkedHashMap;
077import java.util.List;
078import java.util.Map;
079import java.util.Objects;
080import java.util.Set;
081import java.util.concurrent.ExecutionException;
082import java.util.stream.Collectors;
083
084import org.apache.commons.collections4.MapUtils;
085import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
086import org.apache.commons.logging.Log;
087
088import com.google.common.base.Optional;
089import com.google.common.cache.CacheBuilder;
090import com.google.common.cache.CacheLoader;
091import com.google.common.cache.LoadingCache;
092import com.google.common.collect.ArrayListMultimap;
093import com.google.common.collect.ImmutableList;
094import com.google.common.collect.Lists;
095import com.google.common.collect.Maps;
096import com.google.common.collect.Multimap;
097import com.google.common.collect.Sets;
098
099/**
100 * A class which represents the accessible configuration data at a given point in a sitemap.<p>
101 */
102public class CmsADEConfigData {
103
104    /**
105     * Enum for indicating how sitemap attributes should be read.
106     */
107    public enum AttributeMode {
108        /** Include all sitemap attributes, including dynamically injected ones. */
109        all,
110
111        /** Ignore all dynamically injected sitemap attributes. */
112        excludeInjected,
113
114        /** Ignore dynamically injected sitemap attributes for this sitemap configuration, but include injected values that were inherited from parent sitemap configurations. */
115        excludeInjectedForThisLevel
116    }
117
118    /**
119     * Bean which contains the detail information for a single sub-sitemap and resource type.<p>
120     *
121     * This includes both  information about the detail page itself, as well as the path of the folder
122     * which is used to store that content type in this subsitemap.<p>
123     *
124     */
125    public class DetailInfo {
126
127        /** The base path of the sitemap configuration where this information originates from. */
128        private String m_basePath;
129
130        /** The information about the detail page info itself. */
131        private CmsDetailPageInfo m_detailPageInfo;
132
133        /** The content folder path. */
134        private String m_folderPath;
135
136        /** The detail type. */
137        private String m_type;
138
139        /**
140         * Creates a new instance.<p>
141         *
142         * @param folderPath the content folder path
143         * @param detailPageInfo the detail page information
144         * @param type the detail type
145         * @param basePath the base path of the sitemap configuration
146         */
147        public DetailInfo(String folderPath, CmsDetailPageInfo detailPageInfo, String type, String basePath) {
148
149            m_folderPath = folderPath;
150            m_detailPageInfo = detailPageInfo;
151            m_type = type;
152            m_basePath = basePath;
153
154        }
155
156        /**
157         * Gets the base path of the sitemap configuration from which this information is coming.<p>
158         *
159         * @return the base path
160         */
161        public String getBasePath() {
162
163            return m_basePath;
164        }
165
166        /**
167         * Gets the detail page information.<p>
168         *
169         * @return the detail page information
170         */
171        public CmsDetailPageInfo getDetailPageInfo() {
172
173            return m_detailPageInfo;
174        }
175
176        /**
177         * Gets the content folder path.<p>
178         *
179         * @return the content folder path
180         */
181        public String getFolderPath() {
182
183            return m_folderPath;
184        }
185
186        /**
187         * Gets the detail type.<p>
188         *
189         * @return the detail type
190         */
191        public String getType() {
192
193            return m_type;
194        }
195
196        /**
197         * Sets the base path.<p>
198         *
199         * @param basePath the new base path value
200         */
201        public void setBasePath(String basePath) {
202
203            m_basePath = basePath;
204        }
205
206        /**
207         * @see java.lang.Object#toString()
208         */
209        @Override
210        public String toString() {
211
212            return ReflectionToStringBuilder.toString(this);
213        }
214    }
215
216    /** Sitemap attribute for the upload folder. */
217    public static final String ATTR_BINARY_UPLOAD_TARGET = "binary.upload.target";
218
219    /** Sitemap attribute controlling the default files that should be truncated by the link finisher (comma separated). */
220    public static final String ATTR_TEMPLATE_LINK_DEFAULTFILES = "template.link.defaultfiles";
221
222    /** Sitemap attribute configuring the link finisher - currently only supports the mode 'foldername', every other value is interpreted as 'disabled'. */
223    public static final String ATTR_TEMPLATE_LINK_FINISHER = "template.link.finisher";
224
225    /** Sitemap attribute that stores a server prefix for exported resources, only relevant when 'force absolute links' mode enabled. */
226    public static final String ATTRIBUTE_LINK_FORCEABSOLUTE_EXPORTPREFIX = "template.link.forceabsolute.exportprefix";
227
228    /** Attribute for regex to exclude links from being run through the finisher if they match. */
229    public static final String ATTRIBUTE_TEMPLATE_LINK_FINISHER_EXCLUDE = "template.link.finisher.exclude";
230
231    /** Prefix for logging special request log messages. */
232    public static final String REQ_LOG_PREFIX = "[CmsADEConfigData] ";
233
234    /** Channel for logging special request log messages. */
235    public static final String REQUEST_LOG_CHANNEL = "org.opencms.ade.configuration.CmsADEConfigData.request";
236
237    /** The log instance for this class. */
238    private static final Log LOG = CmsLog.getLog(CmsADEConfigData.class);
239
240    /** Prefixes for internal settings which might be passed as formatter keys to findFormatter(). */
241    private static final HashSet<String> systemSettingPrefixes = new HashSet<>(
242        Arrays.asList("element", "model", "source", "use", "cms", "is"));
243
244    /** The wrapped configuration bean containing the actual data. */
245    protected CmsADEConfigDataInternal m_data;
246
247    /** Extra information injected from elsewhere. */
248    protected List<I_CmsSitemapExtraInfo> m_extraInfo = Collections.emptyList();
249
250    /** Lazily initialized map of formatters. */
251    private Map<CmsUUID, I_CmsFormatterBean> m_activeFormatters;
252
253    /** Lazily initialized cache for active formatters by formatter key. */
254    private Multimap<String, I_CmsFormatterBean> m_activeFormattersByKey;
255
256    /** The sitemap attributes (may be null if not yet computed). */
257    private Map<String, AttributeValue> m_attributes;
258
259    /** The cache state to which the wrapped configuration bean belongs. */
260    private CmsADEConfigCacheState m_cache;
261
262    /** Current formatter configuration. */
263    private CmsFormatterConfigurationCacheState m_cachedFormatters;
264
265    /** The configuration sequence (contains the list of all sitemap configuration data beans to be used for inheritance). */
266    private CmsADEConfigurationSequence m_configSequence;
267
268    /** Cache for formatters by container type. */
269    private Map<String, List<I_CmsFormatterBean>> m_formattersByContainerType = new HashMap<>();
270
271    /** Cache for formatters by display type. */
272    private Map<String, List<I_CmsFormatterBean>> m_formattersByDisplayType = new HashMap<>();
273
274    /** Lazily initialized cache for formatters by JSP id. */
275    private Multimap<CmsUUID, I_CmsFormatterBean> m_formattersByJspId;
276
277    /** Lazily initialized cache for formatters by formatter key. */
278    private Multimap<String, I_CmsFormatterBean> m_formattersByKey;
279
280    /** Loading cache for for formatters grouped by type. */
281    private LoadingCache<String, List<I_CmsFormatterBean>> m_formattersByTypeCache = CacheBuilder.newBuilder().build(
282        new CacheLoader<String, List<I_CmsFormatterBean>>() {
283
284            @Override
285            public List<I_CmsFormatterBean> load(String typeName) throws Exception {
286
287                List<I_CmsFormatterBean> result = new ArrayList<>();
288                for (I_CmsFormatterBean formatter : getActiveFormatters().values()) {
289                    if (formatter.getResourceTypeNames().contains(typeName)) {
290                        result.add(formatter);
291                    }
292                }
293                return result;
294            }
295        });
296
297    /** The configured link finisher. */
298    private CmsLinkFinisher m_linkFinisher;
299
300    /** Cached shared setting overrides. */
301    private volatile ImmutableList<CmsUUID> m_sharedSettingOverrides;
302
303    /** Set of names of active types.*/
304    private Set<String> m_typesAddable;
305
306    /** Cache of (active) resource type configurations by name. */
307    private Map<String, CmsResourceTypeConfig> m_typesByName;
308
309    /** Type names configured in this or ancestor sitemap configurations. */
310    private Set<String> m_typesInAncestors;
311
312    /**
313     * Creates a new configuration data object, based on an internal configuration data bean and a
314     * configuration cache state.<p>
315     *
316     * @param data the internal configuration data bean
317     * @param cache the configuration cache state
318     * @param configSequence the configuration sequence
319     */
320    public CmsADEConfigData(
321        CmsADEConfigDataInternal data,
322        CmsADEConfigCacheState cache,
323        CmsADEConfigurationSequence configSequence) {
324
325        m_data = data;
326        m_cache = cache;
327        m_configSequence = configSequence;
328    }
329
330    /**
331     * Generic method to merge lists of named configuration objects.<p>
332     *
333     * The lists are merged such that the configuration objects from the child list rise to the front of the result list,
334     * and two configuration objects will be merged themselves if they share the same name.<p>
335     *
336     * For example, if we have two lists of configuration objects:<p>
337     *
338     * parent: A1, B1, C1<p>
339     * child: D2, B2<p>
340     *
341     * then the resulting list will look like:<p>
342     *
343     * D2, B3, A1, C1<p>
344     *
345     * where B3 is the result of merging B1 and B2.<p>
346     *
347     * @param <C> the type of configuration object
348     * @param parentConfigs the parent configurations
349     * @param childConfigs the child configurations
350     * @param preserveDisabled if true, try to merge parents with disabled children instead of discarding them
351     *
352     * @return the merged configuration object list
353     */
354    public static <C extends I_CmsConfigurationObject<C>> List<C> combineConfigurationElements(
355        List<C> parentConfigs,
356        List<C> childConfigs,
357        boolean preserveDisabled) {
358
359        List<C> result = new ArrayList<C>();
360        Map<String, C> map = new LinkedHashMap<String, C>();
361        if (parentConfigs != null) {
362            for (C parent : Lists.reverse(parentConfigs)) {
363                map.put(parent.getKey(), parent);
364            }
365        }
366        if (childConfigs == null) {
367            childConfigs = Collections.emptyList();
368        }
369        for (C child : Lists.reverse(childConfigs)) {
370            String childKey = child.getKey();
371            if (child.isDisabled() && !preserveDisabled) {
372                map.remove(childKey);
373            } else {
374                C parent = map.get(childKey);
375                map.remove(childKey);
376                C newValue;
377                if (parent != null) {
378                    newValue = parent.merge(child);
379                } else {
380                    newValue = child;
381                }
382                map.put(childKey, newValue);
383            }
384        }
385        result = new ArrayList<C>(map.values());
386        Collections.reverse(result);
387        // those multiple "reverse" calls may a bit confusing. They are there because on the one hand we want to keep the
388        // configuration items from one configuration in the same order as they are defined, on the other hand we want
389        // configuration items from a child configuration to rise to the top of the configuration items.
390
391        // so for example, if the parent configuration has items with the keys A,B,C,E
392        // and the child configuration has items  with the keys C,B,D
393        // we want the items of the combined configuration in the order C,B,D,A,E
394
395        return result;
396    }
397
398    /**
399     * If the given formatter key has a sub-formatter suffix, returns the part before it,
400     * otherwise returns null.
401     *
402     * @param key the formatter key
403     * @return the parent formatter key
404     */
405    public static final String getParentFormatterKey(String key) {
406
407        if (key == null) {
408            return null;
409        }
410        int separatorPos = key.lastIndexOf(CmsGwtConstants.FORMATTER_SUBKEY_SEPARATOR);
411        if (separatorPos == -1) {
412            return null;
413        }
414        return key.substring(0, separatorPos);
415
416    }
417
418    /**
419     * Applies the formatter change sets of this and all parent configurations to a formatter index
420     *
421     * @param formatterIndex the collection of formatters to apply the changes to
422     *
423     * @param formatterCacheState the formatter cache state from which new external formatters should be fetched
424     */
425    public void applyAllFormatterChanges(
426        CmsFormatterIndex formatterIndex,
427        CmsFormatterConfigurationCacheState formatterCacheState) {
428
429        for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) {
430            changeSet.applyToFormatters(formatterIndex, formatterCacheState);
431        }
432    }
433
434    /**
435     * Gets the 'best' formatter for the given ID.<p>
436     *
437     * If the formatter with the ID has a key, then the active formatter with the same key is returned.  Otherwise, the
438     * formatter matching the ID is returned. So being active and having the same key is prioritized over an exact ID match.
439     *
440     * @param id the formatter ID
441     * @return the best formatter the given ID
442     */
443    public I_CmsFormatterBean findFormatter(CmsUUID id) {
444
445        return findFormatter(id, false);
446    }
447
448    /**
449     * Gets the 'best' formatter for the given ID.<p>
450     *
451     * If the formatter with the ID has a key, then the active formatter with the same key is returned.  Otherwise, the
452     * formatter matching the ID is returned. So being active and having the same key is prioritized over an exact ID match.
453     *
454     * @param id the formatter ID
455     * @param noWarn if true, disables warnings
456     * @return the best formatter the given ID
457     */
458    public I_CmsFormatterBean findFormatter(CmsUUID id, boolean noWarn) {
459
460        if (id == null) {
461            return null;
462        }
463
464        CmsFormatterConfigurationCacheState formatterState = getCachedFormatters();
465        I_CmsFormatterBean originalResult = formatterState.getFormatters().get(id);
466        I_CmsFormatterBean result = originalResult;
467        if ((result != null) && (result.getKey() != null)) {
468            String key = result.getKey();
469            I_CmsFormatterBean resultForKey = getFormatterAndWarnIfAmbiguous(getActiveFormattersByKey(), key, noWarn);
470            if (resultForKey != null) {
471                result = resultForKey;
472            } else {
473                String parentKey = getParentFormatterKey(key);
474                if (parentKey != null) {
475                    resultForKey = getFormatterAndWarnIfAmbiguous(getActiveFormattersByKey(), parentKey, noWarn);
476                    if (resultForKey != null) {
477                        result = resultForKey;
478                    }
479                }
480            }
481        }
482
483        if (result != originalResult) {
484            String message = "Using substitute formatter "
485                + getFormatterLabel(result)
486                + " instead of "
487                + getFormatterLabel(originalResult)
488                + " because of matching key.";
489            LOG.debug(message);
490            OpenCmsServlet.withRequestCache(
491                reqCache -> reqCache.addLog(REQUEST_LOG_CHANNEL, "debug", REQ_LOG_PREFIX + message));
492        }
493        return result;
494    }
495
496    /**
497     * Gets the 'best' formatter for the given name.<p>
498     *
499     * The name can be either a formatter key, or a formatter UUID. If it's a key, an active formatter with that key is returned.
500     * If it's a UUID, and the formatter with that UUID has no key, it will be returned. If it does have a key, the active formatter
501     * with that key is returned (so being active and having the same key is prioritized over an exact ID match).
502     *
503     * @param name a formatter name (key or ID)
504     * @return the best formatter for that name, or null if no formatter could be found
505     */
506    public I_CmsFormatterBean findFormatter(String name) {
507
508        return findFormatter(name, false);
509    }
510
511    /**
512     * Gets the 'best' formatter for the given name.<p>
513     *
514     * The name can be either a formatter key, or a formatter UUID. If it's a key, an active formatter with that key is returned.
515     * If it's a UUID, and the formatter with that UUID has no key, it will be returned. If it does have a key, the active formatter
516     * with that key is returned (so being active and having the same key is prioritized over an exact ID match).
517     *
518     * @param name a formatter name (key or ID)
519     * @param noWarn if true, disables warnings
520     * @return the best formatter for that name, or null if no formatter could be found
521     */
522    public I_CmsFormatterBean findFormatter(String name, boolean noWarn) {
523
524        if (name == null) {
525            return null;
526        }
527
528        if (systemSettingPrefixes.contains(name) || name.startsWith(CmsContainerElement.SYSTEM_SETTING_PREFIX)) {
529            if (LOG.isDebugEnabled()) {
530                LOG.debug("System setting prefix used: " + name, new Exception());
531            }
532            return null;
533        }
534
535        if (CmsUUID.isValidUUID(name)) {
536            return findFormatter(new CmsUUID(name), noWarn);
537        }
538
539        if (name.startsWith(CmsFormatterConfig.SCHEMA_FORMATTER_ID)) {
540            return null;
541        }
542
543        Multimap<String, I_CmsFormatterBean> active = getActiveFormattersByKey();
544        I_CmsFormatterBean result = getFormatterAndWarnIfAmbiguous(active, name, noWarn);
545        if (result != null) {
546            return result;
547        }
548
549        String parentName = getParentFormatterKey(name);
550        if (parentName != null) {
551            result = getFormatterAndWarnIfAmbiguous(active, parentName, noWarn);
552            if (result != null) {
553                return result;
554            }
555        }
556
557        if (!noWarn) {
558            String message1 = "No local formatter found for key '"
559                + name
560                + "' at '"
561                + getBasePath()
562                + "', trying inactive formatters";
563            LOG.warn(message1);
564            OpenCmsServlet.withRequestCache(rc -> rc.addLog(REQUEST_LOG_CHANNEL, "warn", REQ_LOG_PREFIX + message1));
565        }
566
567        Multimap<String, I_CmsFormatterBean> all = getFormattersByKey();
568        result = getFormatterAndWarnIfAmbiguous(all, name, noWarn);
569        if (result != null) {
570            return result;
571        }
572
573        if (parentName != null) {
574            result = getFormatterAndWarnIfAmbiguous(all, parentName, noWarn);
575            if (result != null) {
576                return result;
577            }
578        }
579
580        if (!noWarn) {
581            OpenCmsServlet.withRequestCache(
582                rc -> rc.addLog(
583                    REQUEST_LOG_CHANNEL,
584                    "warn",
585                    REQ_LOG_PREFIX + "No formatter found for key '" + name + "' at '" + getBasePath() + "'"));
586        }
587        return null;
588    }
589
590    /**
591     * Gets the active external (non-schema) formatters for this sub-sitemap.<p>
592     *
593     * @return the map of active external formatters by structure id
594     */
595    public Map<CmsUUID, I_CmsFormatterBean> getActiveFormatters() {
596
597        if (m_activeFormatters == null) {
598            CmsFormatterIndex formatterIndex = new CmsFormatterIndex();
599            for (I_CmsFormatterBean formatter : getCachedFormatters().getAutoEnabledFormatters().values()) {
600                formatterIndex.addFormatter(formatter);
601            }
602            applyAllFormatterChanges(formatterIndex, getCachedFormatters());
603            m_activeFormatters = Collections.unmodifiableMap(formatterIndex.getFormattersWithAdditionalKeys());
604        }
605        return m_activeFormatters;
606    }
607
608    /**
609     * Gets the active formatters for a given container type.
610     *
611     * @param containerType a container type
612     *
613     * @return the active formatters for the container type
614     */
615    public List<I_CmsFormatterBean> getActiveFormattersWithContainerType(String containerType) {
616
617        return m_formattersByContainerType.computeIfAbsent(
618            containerType,
619            type -> Collections.unmodifiableList(
620                getActiveFormatters().values().stream().filter(
621                    formatter -> formatter.getContainerTypes().contains(type)).collect(Collectors.toList())));
622    }
623
624    /**
625     * Gets the active formatters for a given display type.
626     *
627     * @param displayType a display type
628     * @return the active formatters for the display type
629     */
630    public List<I_CmsFormatterBean> getActiveFormattersWithDisplayType(String displayType) {
631
632        return m_formattersByDisplayType.computeIfAbsent(
633            displayType,
634            type -> Collections.unmodifiableList(
635                getActiveFormatters().values().stream().filter(
636                    formatter -> Objects.equals(type, formatter.getDisplayType())).collect(Collectors.toList()))
637
638        );
639    }
640
641    /**
642     * Gets the set of names of types active in this sitemap configuration.
643     *
644     * @return the set of type names of active types
645     */
646    public Set<String> getAddableTypeNames() {
647
648        Set<String> result = m_typesAddable;
649        if (result != null) {
650            return result;
651        } else {
652            Set<String> mutableResult = new HashSet<>();
653            for (CmsResourceTypeConfig typeConfig : internalGetResourceTypes(true)) {
654                if (!typeConfig.isAddDisabled()) {
655                    mutableResult.add(typeConfig.getTypeName());
656                }
657            }
658            result = Collections.unmodifiableSet(mutableResult);
659            m_typesAddable = result;
660            return result;
661        }
662    }
663
664    /**
665     * Gets the 'add content' restriction for this configuration.
666     *
667     * @return the 'add content' restriction
668     */
669    public CmsAddContentRestriction getAddContentRestriction() {
670
671        getAncestorTypeNames();
672
673        CmsADEConfigData parentConfig = parent();
674        if (parentConfig == null) {
675            return m_data.getAddContentRestriction();
676        } else {
677            return parentConfig.getAddContentRestriction().merge(m_data.getAddContentRestriction());
678        }
679    }
680
681    /**
682     * Gets the list of all detail pages.<p>
683     *
684     * @return the list of all detail pages
685     */
686    public List<CmsDetailPageInfo> getAllDetailPages() {
687
688        return getAllDetailPages(true);
689    }
690
691    /**
692     * Gets a list of all detail pages.<p>
693     *
694     * @param update if true, this method will try to correct the root paths in the returned objects if the corresponding resources have been moved
695     *
696     * @return the list of all detail pages
697     */
698    public List<CmsDetailPageInfo> getAllDetailPages(boolean update) {
699
700        CmsADEConfigData parentData = parent();
701        List<CmsDetailPageInfo> parentDetailPages;
702        if (parentData != null) {
703            parentDetailPages = parentData.getAllDetailPages(false);
704        } else {
705            parentDetailPages = Collections.emptyList();
706        }
707        List<CmsDetailPageInfo> result = mergeDetailPages(parentDetailPages, m_data.getOwnDetailPages());
708        if (update) {
709            result = updateUris(result);
710        }
711        return result;
712    }
713
714    /**
715     * Gets the set of names of types configured in this or any ancestor sitemap configurations.
716     *
717     * @return the set of type names from all ancestor configurations
718     */
719    public Set<String> getAncestorTypeNames() {
720
721        Set<String> result = m_typesInAncestors;
722        if (result != null) {
723            return result;
724        } else {
725            Set<String> mutableResult = new HashSet<>();
726            for (CmsResourceTypeConfig typeConfig : internalGetResourceTypes(false)) {
727                mutableResult.add(typeConfig.getTypeName());
728            }
729            result = Collections.unmodifiableSet(mutableResult);
730            m_typesInAncestors = result;
731            return result;
732        }
733    }
734
735    /**
736     * Gets the value of an attribute, or a default value
737     *
738     * @param key the attribute key
739     * @param defaultValue the value to return if no attribute with the given name is found
740     *
741     * @return the attribute value
742     */
743    public String getAttribute(String key, String defaultValue) {
744
745        AttributeValue value = getAttributes().get(key);
746        if (value != null) {
747            return value.getValue();
748        } else {
749            return defaultValue;
750        }
751
752    }
753
754    /**
755     * Gets the active sitemap attribute editor configuration.
756     *
757     * @return the active sitemap attribute editor configuration
758     */
759    public CmsSitemapAttributeEditorConfiguration getAttributeEditorConfiguration() {
760
761        CmsUUID id = getAttributeEditorConfigurationId();
762        CmsSitemapAttributeEditorConfiguration result = m_cache.getAttributeEditorConfiguration(id);
763        if (result == null) {
764            result = CmsSitemapAttributeEditorConfiguration.EMPTY;
765        }
766        return result;
767
768    }
769
770    /**
771     * Gets the structure id of the configured sitemap attribute editor configuration.
772     *
773     * @return the structure id of the configured sitemap attribute editor configuration
774     */
775    public CmsUUID getAttributeEditorConfigurationId() {
776
777        CmsADEConfigData parent = parent();
778        CmsUUID result = m_data.getAttributeEditorConfigId();
779        if ((result == null) && (parent != null)) {
780            result = parent.getAttributeEditorConfigurationId();
781        }
782        return result;
783
784    }
785
786    /**
787     * Gets the map of attributes configured for this sitemap, including values inherited from parent sitemaps.
788     *
789     * @return the map of attributes
790     */
791    public Map<String, AttributeValue> getAttributes() {
792
793        return getAttributes(AttributeMode.all);
794    }
795
796    /**
797     * Gets the map of attributes configured for this sitemap, including values inherited from parent sitemaps.
798     *
799     * <p>NOTE: If mode != AttributeMode.all, the result is not cached.
800     *
801     * @param mode indicates how injected attributes should be treated
802     * @return the map of attributes
803     */
804    public Map<String, AttributeValue> getAttributes(AttributeMode mode) {
805
806        if ((m_attributes != null) && (mode == AttributeMode.all)) {
807            return m_attributes;
808        }
809        CmsADEConfigData parentConfig = parent();
810        Map<String, AttributeValue> result = new HashMap<>();
811        AttributeMode parentMode = mode;
812        if (mode == AttributeMode.excludeInjectedForThisLevel) {
813            parentMode = AttributeMode.all;
814        }
815        if (parentConfig != null) {
816            result.putAll(parentConfig.getAttributes(parentMode));
817        }
818
819        for (Map.Entry<String, AttributeValue> entry : m_data.getAttributes().entrySet()) {
820            result.put(entry.getKey(), entry.getValue());
821        }
822
823        if ((getBasePath() != null) && (mode == AttributeMode.all)) {
824            for (I_CmsSitemapExtraInfo extraInfo : m_extraInfo) {
825                result.putAll(MapUtils.emptyIfNull(extraInfo.getAttributes(getBasePath())));
826            }
827        }
828
829        Map<String, AttributeValue> immutableResult = Collections.unmodifiableMap(result);
830        if (mode == AttributeMode.all) {
831            m_attributes = immutableResult;
832        }
833        return immutableResult;
834    }
835
836    /**
837     * Gets the configuration base path.<p>
838     *
839     * For example, if the configuration file is located at /sites/default/.content/.config, the base path is /sites/default.<p>
840     *
841     * @return the base path of the configuration
842     */
843    public String getBasePath() {
844
845        return m_data.getBasePath();
846    }
847
848    /**
849     * Gets the cached formatters.<p>
850     *
851     * @return the cached formatters
852     */
853    public CmsFormatterConfigurationCacheState getCachedFormatters() {
854
855        if (m_cachedFormatters == null) {
856            m_cachedFormatters = OpenCms.getADEManager().getCachedFormatters(
857                getCms().getRequestContext().getCurrentProject().isOnlineProject());
858        }
859        return m_cachedFormatters;
860    }
861
862    /**
863     * Gets an (immutable) list of paths of configuration files in inheritance order.
864     *
865     * @return the list of configuration files
866     */
867    public List<String> getConfigPaths() {
868
869        return m_configSequence.getConfigPaths();
870
871    }
872
873    /**
874     * Returns the names of the bundles configured as workplace bundles in any module configuration.<p>
875     *
876     * @return the names of the bundles configured as workplace bundles in any module configuration.
877     */
878    public Set<String> getConfiguredWorkplaceBundles() {
879
880        Set<String> result = new HashSet<String>();
881        for (CmsResourceTypeConfig config : internalGetResourceTypes(false)) {
882            String bundlename = config.getConfiguredWorkplaceBundle();
883            if (null != bundlename) {
884                result.add(bundlename);
885            }
886        }
887        return result;
888    }
889
890    /**
891     * Gets the active content folder configuration.
892     *
893     * @return the active content folder configuration
894     */
895    public CmsContentFolderOption getContentFolderOption() {
896
897        if (m_data.getContentFolderOption() != null) {
898            return m_data.getContentFolderOption();
899        }
900        CmsADEConfigData parent = parent();
901        if (parent != null) {
902            return parent.getContentFolderOption();
903        } else {
904            return null;
905        }
906    }
907
908    /**
909     * Gets the content folder path.<p>
910     *
911     * For example, if the configuration file is located at /sites/default/.content/.config, the content folder path is /sites/default/.content
912     *
913     * @return the content folder path
914     */
915    public String getContentFolderPath() {
916
917        return CmsStringUtil.joinPaths(m_data.getBasePath(), CmsADEManager.CONTENT_FOLDER_NAME);
918
919    }
920
921    /**
922     * Returns a list of the creatable resource types.<p>
923     *
924     * @param cms the CMS context used to check whether the resource types are creatable
925     * @param pageFolderRootPath the root path of the current container page
926     * @return the list of creatable resource type
927     *
928     * @throws CmsException if something goes wrong
929     */
930    public List<CmsResourceTypeConfig> getCreatableTypes(CmsObject cms, String pageFolderRootPath) throws CmsException {
931
932        List<CmsResourceTypeConfig> result = new ArrayList<CmsResourceTypeConfig>();
933        for (CmsResourceTypeConfig typeConfig : getResourceTypes()) {
934            if (typeConfig.checkCreatable(cms, pageFolderRootPath)) {
935                result.add(typeConfig);
936            }
937        }
938        return result;
939    }
940
941    /**
942     * Returns the default detail page.<p>
943     *
944     * @return the default detail page
945     */
946    public CmsDetailPageInfo getDefaultDetailPage() {
947
948        for (CmsDetailPageInfo detailpage : getAllDetailPages(true)) {
949            if (CmsADEManager.DEFAULT_DETAILPAGE_TYPE.equals(detailpage.getType())) {
950                return detailpage;
951            }
952        }
953        return null;
954    }
955
956    /**
957     * Returns the default model page.<p>
958     *
959     * @return the default model page
960     */
961    public CmsModelPageConfig getDefaultModelPage() {
962
963        List<CmsModelPageConfig> modelPages = getModelPages();
964        for (CmsModelPageConfig modelPageConfig : getModelPages()) {
965            if (modelPageConfig.isDefault()) {
966                return modelPageConfig;
967            }
968        }
969        if (modelPages.isEmpty()) {
970            return null;
971        }
972        return modelPages.get(0);
973    }
974
975    /**
976     * Gets the detail information for this sitemap config data object.<p>
977     *
978     * @param cms the CMS context
979     * @return the list of detail information
980     */
981    public List<DetailInfo> getDetailInfos(CmsObject cms) {
982
983        List<DetailInfo> result = Lists.newArrayList();
984        List<CmsDetailPageInfo> detailPages = getAllDetailPages(true);
985        Collections.reverse(detailPages); // make sure primary detail pages come later in the list and override other detail pages for the same type
986        Map<String, CmsDetailPageInfo> primaryDetailPageMapByType = Maps.newHashMap();
987        for (CmsDetailPageInfo pageInfo : detailPages) {
988            primaryDetailPageMapByType.put(pageInfo.getType(), pageInfo);
989        }
990        for (CmsResourceTypeConfig typeConfig : getResourceTypes()) {
991            String typeName = typeConfig.getTypeName();
992            if (((typeConfig.getFolderOrName() == null) || !typeConfig.getFolderOrName().isPageRelative())
993                && primaryDetailPageMapByType.containsKey(typeName)) {
994                String folderPath = typeConfig.getFolderPath(cms, null);
995                CmsDetailPageInfo pageInfo = primaryDetailPageMapByType.get(typeName);
996                result.add(new DetailInfo(folderPath, pageInfo, typeName, getBasePath()));
997            }
998        }
999        return result;
1000    }
1001
1002    /**
1003     * Gets the detail pages for a specific type.<p>
1004     *
1005     * @param type the type name
1006     *
1007     * @return the list of detail pages for that type
1008     */
1009    public List<CmsDetailPageInfo> getDetailPagesForType(String type) {
1010
1011        List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>();
1012        CmsResourceTypeConfig typeConfig = getResourceType(type);
1013        if (type.startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)
1014            || ((typeConfig != null) && !typeConfig.isDetailPagesDisabled())) {
1015
1016            List<CmsDetailPageInfo> defaultPages = new ArrayList<>();
1017            for (CmsDetailPageInfo detailpage : getAllDetailPages(true)) {
1018                if (detailpage.getType().equals(type)) {
1019                    result.add(detailpage);
1020                } else if (CmsADEManager.DEFAULT_DETAILPAGE_TYPE.equals(detailpage.getType())) {
1021                    defaultPages.add(detailpage);
1022                }
1023            }
1024            result.addAll(defaultPages);
1025        }
1026        return result;
1027    }
1028
1029    /**
1030     * Returns the direct edit permissions for e.g. list elements with the given type.
1031     *
1032     * @param type the resource type name
1033     * @return the permissions
1034     */
1035    public SitemapDirectEditPermissions getDirectEditPermissions(String type) {
1036
1037        if (type == null) {
1038            LOG.error("Null type in checkListEdit");
1039            return SitemapDirectEditPermissions.all;
1040        }
1041
1042        if (!getAncestorTypeNames().contains(type)) {
1043            // not configured anywhere for ADE
1044            return SitemapDirectEditPermissions.editAndCreate;
1045        }
1046
1047        CmsResourceTypeConfig typeConfig = getResourceType(type);
1048        if (typeConfig == null) {
1049            return SitemapDirectEditPermissions.none;
1050        }
1051
1052        if (typeConfig.isEnabledInLists()) {
1053            return SitemapDirectEditPermissions.editAndCreate;
1054        }
1055
1056        if (typeConfig.isCreateDisabled() || typeConfig.isAddDisabled()) {
1057            return SitemapDirectEditPermissions.editOnly;
1058        }
1059
1060        return SitemapDirectEditPermissions.all;
1061    }
1062
1063    /**
1064     * Gets the display mode for deactivated functions in the gallery dialog.
1065     *
1066     * @param defaultValue the default value to return if it's not set
1067     * @return the display mode for deactivated types
1068     */
1069    public CmsGalleryDisabledTypesMode getDisabledFunctionsMode(CmsGalleryDisabledTypesMode defaultValue) {
1070
1071        CmsADEConfigData parentData = parent();
1072        if (m_data.getGalleryDisabledFunctionsMode() != null) {
1073            return m_data.getGalleryDisabledFunctionsMode();
1074        } else if (parentData != null) {
1075            return parentData.getDisabledFunctionsMode(defaultValue);
1076        } else {
1077            return defaultValue;
1078        }
1079    }
1080
1081    /**
1082     * Gets the display mode for deactivated types in the gallery dialog.
1083     *
1084     * @param defaultValue the default value to return if it's not set
1085     * @return the display mode for deactivated types
1086     */
1087    public CmsGalleryDisabledTypesMode getDisabledTypeMode(CmsGalleryDisabledTypesMode defaultValue) {
1088
1089        CmsADEConfigData parentData = parent();
1090        if (m_data.getDisabledTypeMode() != null) {
1091            return m_data.getDisabledTypeMode();
1092        } else if (parentData != null) {
1093            return parentData.getDisabledTypeMode(defaultValue);
1094        } else {
1095            return defaultValue;
1096        }
1097    }
1098
1099    /**
1100     * Returns all available display formatters.<p>
1101     *
1102     * @param cms the cms context
1103     *
1104     * @return the available display formatters
1105     */
1106    public List<I_CmsFormatterBean> getDisplayFormatters(CmsObject cms) {
1107
1108        List<I_CmsFormatterBean> result = new ArrayList<I_CmsFormatterBean>();
1109        for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) {
1110            if (formatter.isDisplayFormatter()) {
1111                result.add(formatter);
1112            }
1113        }
1114        return result;
1115    }
1116
1117    /**
1118     * Gets the bean that represents the dynamic function availability.
1119     *
1120     * @param formatterConfig the formatter configuration state
1121     *
1122     * @return the dynamic function availability
1123     */
1124    public CmsFunctionAvailability getDynamicFunctionAvailability(CmsFormatterConfigurationCacheState formatterConfig) {
1125
1126        CmsADEConfigData parentData = parent();
1127        CmsFunctionAvailability result;
1128        if (parentData == null) {
1129            result = new CmsFunctionAvailability(formatterConfig);
1130        } else {
1131            result = parentData.getDynamicFunctionAvailability(formatterConfig);
1132        }
1133        Collection<CmsUUID> enabledIds = m_data.getDynamicFunctions();
1134        Collection<CmsUUID> disabledIds = m_data.getFunctionsToRemove();
1135        if (m_data.isRemoveAllFunctions() && !m_configSequence.getMeta().isSkipRemovals()) {
1136            result.removeAll();
1137        }
1138        if (enabledIds != null) {
1139            result.addAll(enabledIds);
1140        }
1141        if (disabledIds != null) {
1142            for (CmsUUID id : disabledIds) {
1143                result.remove(id);
1144            }
1145        }
1146        return result;
1147    }
1148
1149    /**
1150     * Gets the root path of the closest subsite going up the tree which has the 'exclude external detail contents' option enabled, or '/' if no such subsite exists.
1151     *
1152     * @return the root path of the closest subsite with 'external detail contents excluded'
1153     */
1154    public String getExternalDetailContentExclusionFolder() {
1155
1156        if (m_data.isExcludeExternalDetailContents()) {
1157            String basePath = m_data.getBasePath();
1158            if (basePath == null) {
1159                return "/";
1160            } else {
1161                return basePath;
1162            }
1163        } else {
1164            CmsADEConfigData parent = parent();
1165            if (parent != null) {
1166                return parent.getExternalDetailContentExclusionFolder();
1167            } else {
1168                return "/";
1169            }
1170        }
1171    }
1172
1173    /**
1174     * Returns the formatter change sets for this and all parent sitemaps, ordered by increasing folder depth of the sitemap.<p>
1175     *
1176     * @return the formatter change sets for all ancestor sitemaps
1177     */
1178    public List<CmsFormatterChangeSet> getFormatterChangeSets() {
1179
1180        CmsADEConfigData currentConfig = this;
1181        List<CmsFormatterChangeSet> result = Lists.newArrayList();
1182        while (currentConfig != null) {
1183            CmsFormatterChangeSet changes = currentConfig.getOwnFormatterChangeSet();
1184            if (changes != null) {
1185                if (currentConfig.getMeta().isSkipRemovals()) {
1186                    changes = changes.cloneWithNoRemovals();
1187                }
1188                result.add(changes);
1189            }
1190            currentConfig = currentConfig.parent();
1191        }
1192        Collections.reverse(result);
1193        return result;
1194    }
1195
1196    /**
1197     * Gets the formatter configuration for a resource.<p>
1198     *
1199     * @param cms the current CMS context
1200     * @param res the resource for which the formatter configuration should be retrieved
1201     *
1202     * @return the configuration of formatters for the resource
1203     */
1204    public CmsFormatterConfiguration getFormatters(CmsObject cms, CmsResource res) {
1205
1206        if (CmsResourceTypeFunctionConfig.isFunction(res)) {
1207
1208            CmsFormatterConfigurationCacheState formatters = getCachedFormatters();
1209            I_CmsFormatterBean function = findFormatter(res.getStructureId());
1210            if (function != null) {
1211                return CmsFormatterConfiguration.create(cms, Collections.singletonList(function));
1212            } else {
1213                if ((!res.getStructureId().isNullUUID())
1214                    && cms.existsResource(res.getStructureId(), CmsResourceFilter.IGNORE_EXPIRATION)) {
1215                    // usually if it's just been created, but not added to the configuration cache yet
1216                    CmsFormatterBeanParser parser = new CmsFormatterBeanParser(cms, new HashMap<>());
1217                    try {
1218                        function = parser.parse(
1219                            CmsXmlContentFactory.unmarshal(cms, cms.readFile(res)),
1220                            res.getRootPath(),
1221                            "" + res.getStructureId());
1222                        return CmsFormatterConfiguration.create(cms, Collections.singletonList(function));
1223                    } catch (Exception e) {
1224                        LOG.warn(e.getLocalizedMessage(), e);
1225                        return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
1226                    }
1227
1228                } else {
1229                    // if a new function has been dragged on the page, it doesn't exist in the VFS yet, so we need a different
1230                    // instance as a replacement
1231                    CmsResource defaultFormatter = CmsFunctionRenderer.getDefaultFunctionInstance(cms);
1232                    if (defaultFormatter != null) {
1233                        I_CmsFormatterBean defaultFormatterBean = formatters.getFormatters().get(
1234                            defaultFormatter.getStructureId());
1235                        return CmsFormatterConfiguration.create(cms, Collections.singletonList(defaultFormatterBean));
1236                    } else {
1237                        LOG.warn("Could not read default formatter for functions.");
1238                        return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
1239                    }
1240                }
1241            }
1242        } else {
1243            try {
1244                int resTypeId = res.getTypeId();
1245                return getFormatters(
1246                    cms,
1247                    OpenCms.getResourceManager().getResourceType(resTypeId),
1248                    getFormattersFromSchema(cms, res));
1249            } catch (CmsLoaderException e) {
1250                LOG.warn(e.getLocalizedMessage(), e);
1251                return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
1252            }
1253        }
1254    }
1255
1256    /**
1257     * Gets a named function reference.<p>
1258     *
1259     * @param name the name of the function reference
1260     *
1261     * @return the function reference for the given name
1262     */
1263    public CmsFunctionReference getFunctionReference(String name) {
1264
1265        List<CmsFunctionReference> functionReferences = getFunctionReferences();
1266        for (CmsFunctionReference functionRef : functionReferences) {
1267            if (functionRef.getName().equals(name)) {
1268                return functionRef;
1269            }
1270        }
1271        return null;
1272    }
1273
1274    /**
1275     * Gets the list of configured function references.<p>
1276     *
1277     * @return the list of configured function references
1278     */
1279    public List<CmsFunctionReference> getFunctionReferences() {
1280
1281        return internalGetFunctionReferences();
1282    }
1283
1284    /**
1285     * Gets the map of external (non-schema) formatters which are inactive in this sub-sitemap.<p>
1286     *
1287     * @return the map inactive external formatters
1288     */
1289    public Map<CmsUUID, I_CmsFormatterBean> getInactiveFormatters() {
1290
1291        CmsFormatterConfigurationCacheState cacheState = getCachedFormatters();
1292        Map<CmsUUID, I_CmsFormatterBean> result = Maps.newHashMap(cacheState.getFormatters());
1293        result.keySet().removeAll(getActiveFormatters().keySet());
1294        return result;
1295    }
1296
1297    /**
1298     * Creates a link finisher based on the configured sitemap attributes.
1299     *
1300     * @return the link finisher
1301     */
1302    public CmsLinkFinisher getLinkFinisher() {
1303
1304        if (m_linkFinisher == null) {
1305
1306            boolean enabled = "foldername".equals(getAttribute(ATTR_TEMPLATE_LINK_FINISHER, ""));
1307            String suffixesStr = getAttribute(ATTR_TEMPLATE_LINK_DEFAULTFILES, null);
1308            Collection<String> defaultFileNames;
1309            if (suffixesStr != null) {
1310                defaultFileNames = Collections.unmodifiableList(
1311                    Arrays.asList(suffixesStr.split(",")).stream().map(suffix -> suffix.strip()).filter(
1312                        suffix -> !CmsStringUtil.isEmptyOrWhitespaceOnly(suffix)).collect(Collectors.toList()));
1313            } else {
1314                defaultFileNames = OpenCms.getDefaultFiles();
1315            }
1316            String exclude = getAttribute(ATTRIBUTE_TEMPLATE_LINK_FINISHER_EXCLUDE, null);
1317            m_linkFinisher = new CmsLinkFinisher(enabled, defaultFileNames, exclude);
1318        }
1319        return m_linkFinisher;
1320
1321    }
1322
1323    /**
1324     * In case the 'force absolute links' mode is enabled, returns the optional server prefix for exported resources.
1325     * If there is a server prefix configured here, this overrides any RFS rule configured for exported resources.
1326     * @param cms the CMS context
1327     * @return the optional server prefix for exported resources
1328     */
1329    public String getLinkForceAbsoluteExportPrefix(CmsObject cms) {
1330
1331        if (cms.getRequestContext().isForceAbsoluteLinks()) {
1332            return getAttribute(CmsADEConfigData.ATTRIBUTE_LINK_FORCEABSOLUTE_EXPORTPREFIX, null);
1333        } else {
1334            return null;
1335        }
1336    }
1337
1338    /**
1339     * Gets the list of available model pages.<p>
1340     *
1341     * @return the list of available model pages
1342     */
1343    public List<CmsModelPageConfig> getModelPages() {
1344
1345        return getModelPages(false);
1346    }
1347
1348    /**
1349     * Gets the list of available model pages.<p>
1350     *
1351     * @param includeDisable <code>true</code> to include disabled model pages
1352     *
1353     * @return the list of available model pages
1354     */
1355    public List<CmsModelPageConfig> getModelPages(boolean includeDisable) {
1356
1357        CmsADEConfigData parentData = parent();
1358        List<CmsModelPageConfig> parentModelPages;
1359        if ((parentData != null) && !m_data.isDiscardInheritedModelPages()) {
1360            parentModelPages = parentData.getModelPages();
1361        } else {
1362            parentModelPages = Collections.emptyList();
1363        }
1364
1365        List<CmsModelPageConfig> result = combineConfigurationElements(
1366            parentModelPages,
1367            m_data.getOwnModelPageConfig(),
1368            includeDisable);
1369        return result;
1370    }
1371
1372    /**
1373     * Gets the formatter changes for this sitemap configuration.<p>
1374     *
1375     * @return the formatter change set
1376     */
1377    public CmsFormatterChangeSet getOwnFormatterChangeSet() {
1378
1379        return m_data.getFormatterChangeSet();
1380    }
1381
1382    /**
1383     * Looks up a path dependent secret in the secret store for the current sitemap config path.
1384     *
1385     *  <p>This is done as follows:
1386     *  <p>For the root path of each subsitemap we are in, in ascending order, the key (prefix + &quot;.&quot; + path) is looked up.
1387     *  The first match will be returned. As a special case, the key for the site root is always looked up, even if it's not configured as
1388     *  a sitemap.
1389     *
1390     * @param prefix the key prefix
1391     * @return the secret (or null, if nothing was found)
1392     */
1393    public String getPathDependentSecret(String prefix) {
1394
1395        if (getBasePath() == null) {
1396            return null;
1397        }
1398        List<String> paths = OpenCms.getADEManager().getPathsForSecretLookup(getCms(), getBasePath());
1399        for (String path : paths) {
1400            String secret = OpenCms.getSecretStore().getSecret(prefix + "." + path);
1401            if (secret != null) {
1402                return secret;
1403            }
1404        }
1405        return null;
1406    }
1407
1408    /**
1409     * Gets the configuration for the available properties.<p>
1410     *
1411     * @return the configuration for the available properties
1412     */
1413    public List<CmsPropertyConfig> getPropertyConfiguration() {
1414
1415        CmsADEConfigData parentData = parent();
1416        List<CmsPropertyConfig> parentProperties;
1417        boolean removeInherited = m_data.isDiscardInheritedProperties() && !getMeta().isSkipRemovals();
1418        if ((parentData != null) && !removeInherited) {
1419            parentProperties = parentData.getPropertyConfiguration();
1420        } else {
1421            parentProperties = Collections.emptyList();
1422        }
1423        LinkedHashMap<String, CmsPropertyConfig> propMap = new LinkedHashMap<>();
1424        for (CmsPropertyConfig conf : parentProperties) {
1425            if (conf.isDisabled()) {
1426                continue;
1427            }
1428            propMap.put(conf.getName(), conf);
1429        }
1430        for (CmsPropertyConfig conf : m_data.getOwnPropertyConfigurations()) {
1431            if (conf.isDisabled()) {
1432                propMap.remove(conf.getName());
1433            } else if (propMap.containsKey(conf.getName())) {
1434                propMap.put(conf.getName(), propMap.get(conf.getName()).merge(conf));
1435            } else {
1436                propMap.put(conf.getName(), conf);
1437            }
1438        }
1439        List<CmsPropertyConfig> result = new ArrayList<>(propMap.values());
1440        return result;
1441    }
1442
1443    /**
1444     * Computes the ordered map of properties to display in the property dialog, given the map of default property configurations passed as a parameter.
1445     *
1446     * @param defaultProperties the default property configurations
1447     * @return the ordered map of property configurations for the property dialog
1448     */
1449    public Map<String, CmsXmlContentProperty> getPropertyConfiguration(
1450        Map<String, CmsXmlContentProperty> defaultProperties) {
1451
1452        List<CmsPropertyConfig> myPropConfigs = getPropertyConfiguration();
1453        Map<String, CmsXmlContentProperty> allProps = new LinkedHashMap<>(defaultProperties);
1454        Map<String, CmsXmlContentProperty> result = new LinkedHashMap<>();
1455        for (CmsPropertyConfig prop : myPropConfigs) {
1456            allProps.put(prop.getName(), prop.getPropertyData());
1457            if (prop.isTop()) {
1458                result.put(prop.getName(), prop.getPropertyData());
1459            }
1460        }
1461        for (Map.Entry<String, CmsXmlContentProperty> entry : allProps.entrySet()) {
1462            if (!result.containsKey(entry.getKey())) {
1463                result.put(entry.getKey(), entry.getValue());
1464            }
1465        }
1466        return result;
1467
1468    }
1469
1470    /**
1471     * Gets the property configuration as a map of CmsXmlContentProperty instances.<p>
1472     *
1473     * @return the map of property configurations
1474     */
1475    public Map<String, CmsXmlContentProperty> getPropertyConfigurationAsMap() {
1476
1477        Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>();
1478        for (CmsPropertyConfig propConf : getPropertyConfiguration()) {
1479            result.put(propConf.getName(), propConf.getPropertyData());
1480        }
1481        return result;
1482    }
1483
1484    /**
1485     * Returns the resource from which this configuration was read.<p>
1486     *
1487     * @return the resource from which this configuration was read
1488     */
1489    public CmsResource getResource() {
1490
1491        return m_data.getResource();
1492    }
1493
1494    /**
1495     * Returns the configuration for a specific resource type.<p>
1496     *
1497     * @param typeName the name of the type
1498     *
1499     * @return the resource type configuration for that type
1500     */
1501    public CmsResourceTypeConfig getResourceType(String typeName) {
1502
1503        for (CmsResourceTypeConfig type : getResourceTypes()) {
1504            if (typeName.equals(type.getTypeName())) {
1505                return type;
1506            }
1507        }
1508        return null;
1509    }
1510
1511    /**
1512     * Gets a list of all available resource type configurations.<p>
1513     *
1514     * @return the available resource type configurations
1515     */
1516    public List<CmsResourceTypeConfig> getResourceTypes() {
1517
1518        List<CmsResourceTypeConfig> result = internalGetResourceTypes(true);
1519        for (CmsResourceTypeConfig config : result) {
1520            config.initialize(getCms());
1521        }
1522        return result;
1523    }
1524
1525    /**
1526     * Gets the searchable resource type configurations.<p>
1527     *
1528     * @param cms the current CMS context
1529     * @return the searchable resource type configurations
1530     */
1531    public Collection<CmsResourceTypeConfig> getSearchableTypes(CmsObject cms) {
1532
1533        return getResourceTypes();
1534    }
1535
1536    /**
1537     * Gets the list of structure ids of the shared setting overrides, ordered by increasing specificity.
1538     *
1539     * @return the list of structure ids of shared setting overrides
1540     */
1541    public ImmutableList<CmsUUID> getSharedSettingOverrides() {
1542
1543        if (m_sharedSettingOverrides != null) {
1544            return m_sharedSettingOverrides;
1545        }
1546
1547        CmsADEConfigData currentConfig = this;
1548        List<CmsADEConfigData> relevantConfigurations = new ArrayList<>();
1549        while (currentConfig != null) {
1550            relevantConfigurations.add(currentConfig);
1551            if (currentConfig.m_data.isRemoveSharedSettingOverrides()
1552                && !currentConfig.m_configSequence.getMeta().isSkipRemovals()) {
1553                // once we find a configuration where 'remove all shared setting overrides' is enabled,
1554                // all parent configurations become irrelevant
1555                break;
1556            }
1557            currentConfig = currentConfig.parent();
1558        }
1559
1560        // order by ascending specificity
1561        Collections.reverse(relevantConfigurations);
1562
1563        List<CmsUUID> ids = new ArrayList<>();
1564        for (CmsADEConfigData config : relevantConfigurations) {
1565            CmsUUID id = config.m_data.getSharedSettingOverride();
1566            if (id != null) {
1567                ids.add(id);
1568            }
1569        }
1570        ImmutableList<CmsUUID> result = ImmutableList.copyOf(ids);
1571        m_sharedSettingOverrides = result;
1572        return result;
1573    }
1574
1575    /**
1576     * Gets the ids of site plugins which are active in this sitemap configuration.
1577     *
1578     * @return the ids of active site plugins
1579     */
1580    public Set<CmsUUID> getSitePluginIds() {
1581
1582        CmsADEConfigData parent = parent();
1583        Set<CmsUUID> result;
1584        if ((parent == null) || (m_data.isRemoveAllPlugins() && !getMeta().isSkipRemovals())) {
1585            result = new HashSet<>();
1586        } else {
1587            result = parent.getSitePluginIds();
1588        }
1589        result.removeAll(m_data.getRemovedPlugins());
1590        result.addAll(m_data.getAddedPlugins());
1591        return result;
1592    }
1593
1594    /**
1595     * Gets the list of site plugins active in this sitemap configuration.
1596     *
1597     * @return the list of active site plugins
1598     */
1599    public List<CmsSitePlugin> getSitePlugins() {
1600
1601        Set<CmsUUID> pluginIds = getSitePluginIds();
1602        List<CmsSitePlugin> result = new ArrayList<>();
1603        Map<CmsUUID, CmsSitePlugin> plugins = m_cache.getSitePlugins();
1604        for (CmsUUID id : pluginIds) {
1605            CmsSitePlugin sitePlugin = plugins.get(id);
1606            if (sitePlugin != null) {
1607                result.add(sitePlugin);
1608            }
1609        }
1610        return result;
1611    }
1612
1613    /**
1614     * Gets the type ordering mode.
1615     *
1616     * @return the type ordering mode
1617     */
1618    public CmsTypeOrderingMode getTypeOrderingMode() {
1619
1620        CmsTypeOrderingMode ownOrderingMode = m_data.getTypeOrderingMode();
1621        if (ownOrderingMode != null) {
1622            return ownOrderingMode;
1623        } else {
1624            CmsADEConfigData parentConfig = parent();
1625            CmsTypeOrderingMode parentMode = null;
1626            if (parentConfig == null) {
1627                parentMode = CmsTypeOrderingMode.latestOnTop;
1628            } else {
1629                parentMode = parentConfig.getTypeOrderingMode();
1630            }
1631            return parentMode;
1632        }
1633
1634    }
1635
1636    /**
1637     * Gets a map of the active resource type configurations, with type names as keys.
1638     *
1639     * @return the map of active types
1640     */
1641    public Map<String, CmsResourceTypeConfig> getTypesByName() {
1642
1643        if (m_typesByName != null) {
1644            return m_typesByName;
1645        }
1646        Map<String, CmsResourceTypeConfig> result = new HashMap<>();
1647        for (CmsResourceTypeConfig type : getResourceTypes()) {
1648            result.put(type.getTypeName(), type);
1649        }
1650        result = Collections.unmodifiableMap(result);
1651        m_typesByName = result;
1652        return result;
1653    }
1654
1655    /**
1656     * Gets the set of resource type names for which schema formatters can be enabled or disabled and which are not disabled in this sub-sitemap.<p>
1657     *
1658     * @return the set of types for which schema formatters are active
1659     */
1660    public Set<String> getTypesWithActiveSchemaFormatters() {
1661
1662        Set<String> result = Sets.newHashSet(getTypesWithModifiableFormatters());
1663        for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) {
1664            changeSet.applyToTypes(result);
1665        }
1666        return result;
1667    }
1668
1669    /**
1670     * Gets the set of names of resource types which have schema-based formatters that can be enabled or disabled.<p>
1671     *
1672     * @return the set of names of resource types which have schema-based formatters that can be enabled or disabled
1673     */
1674    public Set<String> getTypesWithModifiableFormatters() {
1675
1676        Set<String> result = new HashSet<String>();
1677        for (I_CmsResourceType type : OpenCms.getResourceManager().getResourceTypes()) {
1678            if (type instanceof CmsResourceTypeXmlContent) {
1679                CmsXmlContentDefinition contentDef = null;
1680                try {
1681                    contentDef = CmsXmlContentDefinition.getContentDefinitionForType(getCms(), type.getTypeName());
1682                    if ((contentDef != null) && contentDef.getContentHandler().hasModifiableFormatters()) {
1683                        result.add(type.getTypeName());
1684                    }
1685                } catch (Exception e) {
1686                    LOG.error(e.getLocalizedMessage(), e);
1687                }
1688            }
1689        }
1690        return result;
1691
1692    }
1693
1694    /**
1695     * Checks if there are any matching formatters for the given set of containers.<p>
1696     *
1697     * @param cms the current CMS context
1698     * @param resType the resource type for which the formatter configuration should be retrieved
1699     * @param containers the page containers
1700     *
1701     * @return if there are any matching formatters
1702     */
1703    public boolean hasFormatters(CmsObject cms, I_CmsResourceType resType, Collection<CmsContainer> containers) {
1704
1705        try {
1706            if (CmsXmlDynamicFunctionHandler.TYPE_FUNCTION.equals(resType.getTypeName())
1707                || CmsResourceTypeFunctionConfig.TYPE_NAME.equals(resType.getTypeName())) {
1708                // dynamic function may match any container
1709                return true;
1710            }
1711            CmsXmlContentDefinition def = CmsXmlContentDefinition.getContentDefinitionForType(
1712                cms,
1713                resType.getTypeName());
1714            CmsFormatterConfiguration schemaFormatters = def.getContentHandler().getFormatterConfiguration(cms, null);
1715            CmsFormatterConfiguration formatters = getFormatters(cms, resType, schemaFormatters);
1716            for (CmsContainer cont : containers) {
1717                if (cont.isEditable()
1718                    && (formatters.getAllMatchingFormatters(cont.getType(), cont.getWidth()).size() > 0)) {
1719                    return true;
1720                }
1721            }
1722        } catch (CmsException e) {
1723            LOG.warn(e.getLocalizedMessage(), e);
1724
1725        }
1726        return false;
1727    }
1728
1729    /**
1730     * Returns the value of the "create contents locally" flag.<p>
1731     *
1732     * If this flag is set, contents of types configured in a super-sitemap will be created in the sub-sitemap (if the user
1733     * creates them from the sub-sitemap).
1734     *
1735     * @return the "create contents locally" flag
1736     */
1737    public boolean isCreateContentsLocally() {
1738
1739        return m_data.isCreateContentsLocally();
1740    }
1741
1742    /**
1743     * Returns the value of the "discard inherited model pages" flag.<p>
1744     *
1745     * If this flag is set, inherited model pages will be discarded for this sitemap.<p>
1746     *
1747     * @return the "discard inherited model pages" flag
1748     */
1749    public boolean isDiscardInheritedModelPages() {
1750
1751        return m_data.isDiscardInheritedModelPages();
1752    }
1753
1754    /**
1755     * Returns the value of the "discard inherited properties" flag.<p>
1756     *
1757     * If this is flag is set, inherited property definitions will be discarded for this sitemap.<p>
1758     *
1759     * @return the "discard inherited properties" flag.<p>
1760     */
1761    public boolean isDiscardInheritedProperties() {
1762
1763        return m_data.isDiscardInheritedProperties();
1764    }
1765
1766    /**
1767     * Returns the value of the "discard inherited types" flag.<p>
1768     *
1769     * If this flag is set, inherited resource types from a super-sitemap will be discarded for this sitemap.<p>
1770     *
1771     * @return the "discard inherited types" flag
1772     */
1773    public boolean isDiscardInheritedTypes() {
1774
1775        return m_data.isDiscardInheritedTypes();
1776    }
1777
1778    /**
1779     * True if detail contents outside this sitemap should not be rendered in detail pages from this sitemap.
1780     *
1781     * @return true if detail contents outside this sitemap should not be rendered in detail pages from this sitemap.
1782     */
1783    public boolean isExcludeExternalDetailContents() {
1784
1785        return m_data.isExcludeExternalDetailContents();
1786    }
1787
1788    /**
1789     * Checks if dynamic functions not matching any containers should be hidden.
1790     *
1791     * @return true if dynamic functions not matching any containers should be hidden
1792     */
1793    public boolean isHideNonMatchingFunctions() {
1794
1795        return getDisabledFunctionsMode(CmsGalleryDisabledTypesMode.hide) == CmsGalleryDisabledTypesMode.hide;
1796    }
1797
1798    /**
1799     * Returns true if the subsite should be included in the site selector.
1800     *
1801     * @return true if the subsite should be included in the site selector
1802     */
1803    public boolean isIncludeInSiteSelector() {
1804
1805        return m_configSequence.getConfig().isIncludeInSiteSelector();
1806    }
1807
1808    /**
1809     * Returns true if this is a module configuration instead of a normal sitemap configuration.<p>
1810     *
1811     * @return true if this is a module configuration
1812     */
1813    public boolean isModuleConfiguration() {
1814
1815        return m_data.isModuleConfig();
1816    }
1817
1818    /**
1819     * Returns true if detail pages from this sitemap should be preferred for links to contents in this sitemap.<p>
1820     *
1821     * @return true if detail pages from this sitemap should be preferred for links to contents in this sitemap
1822     */
1823    public boolean isPreferDetailPagesForLocalContents() {
1824
1825        return m_data.isPreferDetailPagesForLocalContents();
1826    }
1827
1828    /**
1829     * Checks if any formatter with the given JSP id has the 'search content' option set to true.
1830     *
1831     * @param jspId the structure id of a formatter JSP
1832     * @return true if any of the formatters
1833     */
1834    public boolean isSearchContentFormatter(CmsUUID jspId) {
1835
1836        for (I_CmsFormatterBean formatter : getFormattersByJspId().get(jspId)) {
1837            if (formatter.isSearchContent()) {
1838                return true;
1839            }
1840        }
1841        return false;
1842    }
1843
1844    /**
1845     * Returns true if the new container page format, which uses formatter keys (but also is different in other ways from the new format
1846     *
1847     * @return true if formatter keys should be used
1848     */
1849    public boolean isUseFormatterKeys() {
1850
1851        Boolean result = m_data.getUseFormatterKeys();
1852        if (result != null) {
1853            LOG.debug("isUseFormatterKeys - found value " + result + " at " + getBasePath());
1854            return result.booleanValue();
1855        }
1856        CmsADEConfigData parent = parent();
1857        if (parent != null) {
1858            return parent.isUseFormatterKeys();
1859        }
1860        boolean defaultValue = true;
1861        LOG.debug("isUseFormatterKeys - using defaultValue " + defaultValue);
1862        return defaultValue;
1863    }
1864
1865    /**
1866     * Fetches the parent configuration of this configuration.<p>
1867     *
1868     * If this configuration is a sitemap configuration with no direct parent configuration,
1869     * the module configuration will be returned. If this configuration already is a module configuration,
1870     * null will be returned.<p>
1871     *
1872     * @return the parent configuration
1873     */
1874    public CmsADEConfigData parent() {
1875
1876        Optional<CmsADEConfigurationSequence> parentPath = m_configSequence.getParent();
1877        if (parentPath.isPresent()) {
1878            CmsADEConfigDataInternal internalData = parentPath.get().getConfig();
1879            CmsADEConfigData result = new CmsADEConfigData(internalData, m_cache, parentPath.get());
1880            result.m_extraInfo = m_extraInfo;
1881            return result;
1882        } else {
1883            return null;
1884        }
1885    }
1886
1887    /**
1888     * Returns true if the sitemap attribute editor should be available in this subsite.
1889     *
1890     * @return true if the sitemap attribute editor dialog should be available
1891     */
1892    public boolean shouldShowSitemapAttributeDialog() {
1893
1894        return getAttributeEditorConfiguration().getAttributeDefinitions().size() > 0;
1895    }
1896
1897    /**
1898     * Clears the internal formatter caches.
1899     *
1900     * <p>This should only be used for test cases.
1901     */
1902    protected void clearCaches() {
1903
1904        m_activeFormatters = null;
1905        m_activeFormattersByKey = null;
1906        m_formattersByKey = null;
1907        m_formattersByJspId = null;
1908        m_formattersByTypeCache.invalidateAll();
1909    }
1910
1911    /**
1912     * Creates the content directory for this configuration node if possible.<p>
1913     *
1914     * @throws CmsException if something goes wrong
1915     */
1916    protected void createContentDirectory() throws CmsException {
1917
1918        if (!isModuleConfiguration()) {
1919            String contentFolder = getContentFolderPath();
1920            if (!getCms().existsResource(contentFolder)) {
1921                getCms().createResource(
1922                    contentFolder,
1923                    OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.getStaticTypeName()));
1924            }
1925        }
1926    }
1927
1928    /**
1929     * Gets the CMS object used for VFS operations.<p>
1930     *
1931     * @return the CMS object used for VFS operations
1932     */
1933    protected CmsObject getCms() {
1934
1935        return m_cache.getCms();
1936    }
1937
1938    /**
1939     * Gets the CMS object used for VFS operations.<p>
1940     *
1941     * @return the CMS object
1942     */
1943    protected CmsObject getCmsObject() {
1944
1945        return getCms();
1946    }
1947
1948    /**
1949     * Helper method to converts a list of detail pages to a map from type names to lists of detail pages for each type.<p>
1950     *
1951     * @param detailPages the list of detail pages
1952     *
1953     * @return the map of detail pages
1954     */
1955    protected Map<String, List<CmsDetailPageInfo>> getDetailPagesMap(List<CmsDetailPageInfo> detailPages) {
1956
1957        Map<String, List<CmsDetailPageInfo>> result = Maps.newHashMap();
1958        for (CmsDetailPageInfo detailpage : detailPages) {
1959            String type = detailpage.getType();
1960            if (!result.containsKey(type)) {
1961                result.put(type, new ArrayList<CmsDetailPageInfo>());
1962            }
1963            result.get(type).add(detailpage);
1964        }
1965        return result;
1966    }
1967
1968    /**
1969     * Collects the folder types in a map.<p>
1970     *
1971     * @return the map of folder types
1972     *
1973     * @throws CmsException if something goes wrong
1974     */
1975    protected Map<String, String> getFolderTypes() throws CmsException {
1976
1977        Map<String, String> result = new HashMap<String, String>();
1978        CmsObject cms = OpenCms.initCmsObject(getCms());
1979        if (m_data.isModuleConfig()) {
1980            Set<String> siteRoots = OpenCms.getSiteManager().getSiteRoots();
1981            for (String siteRoot : siteRoots) {
1982                cms.getRequestContext().setSiteRoot(siteRoot);
1983                for (CmsResourceTypeConfig config : getResourceTypes()) {
1984                    if (!config.isDetailPagesDisabled()) {
1985                        String typeName = config.getTypeName();
1986                        if (!config.isPageRelative()) { // elements stored with container pages can not be used as detail contents
1987                            String folderPath = config.getFolderPath(cms, null);
1988                            result.put(CmsStringUtil.joinPaths(folderPath, "/"), typeName);
1989                        }
1990                    }
1991                }
1992            }
1993        } else {
1994            for (CmsResourceTypeConfig config : getResourceTypes()) {
1995                if (!config.isDetailPagesDisabled()) {
1996                    String typeName = config.getTypeName();
1997                    if (!config.isPageRelative()) { // elements stored with container pages can not be used as detail contents
1998                        String folderPath = config.getFolderPath(getCms(), null);
1999                        result.put(CmsStringUtil.joinPaths(folderPath, "/"), typeName);
2000                    }
2001                }
2002            }
2003        }
2004        return result;
2005    }
2006
2007    /**
2008     * Gets the formatter configuration for a resource type.<p>
2009     *
2010     * @param cms the current CMS context
2011     * @param resType the resource type
2012     * @param schemaFormatters the resource schema formatters
2013     *
2014     * @return the configuration of formatters for the resource type
2015     */
2016    protected CmsFormatterConfiguration getFormatters(
2017        CmsObject cms,
2018        I_CmsResourceType resType,
2019        CmsFormatterConfiguration schemaFormatters) {
2020
2021        String typeName = resType.getTypeName();
2022        List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>();
2023        Set<String> types = new HashSet<String>();
2024        types.add(typeName);
2025        for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) {
2026            if (changeSet != null) {
2027                changeSet.applyToTypes(types);
2028            }
2029        }
2030
2031        if ((schemaFormatters != null) && types.contains(typeName)) {
2032            for (I_CmsFormatterBean formatter : schemaFormatters.getAllFormatters()) {
2033                formatters.add(formatter);
2034            }
2035        }
2036
2037        try {
2038            List<I_CmsFormatterBean> formattersForType = m_formattersByTypeCache.get(typeName);
2039            formatters.addAll(formattersForType);
2040        } catch (ExecutionException e) {
2041            LOG.error(e.getLocalizedMessage(), e);
2042
2043        }
2044        return CmsFormatterConfiguration.create(cms, formatters);
2045    }
2046
2047    /**
2048     * Gets the formatters from the schema.<p>
2049     *
2050     * @param cms the current CMS context
2051     * @param res the resource for which the formatters should be retrieved
2052     *
2053     * @return the formatters from the schema
2054     */
2055    protected CmsFormatterConfiguration getFormattersFromSchema(CmsObject cms, CmsResource res) {
2056
2057        try {
2058            return OpenCms.getResourceManager().getResourceType(res.getTypeId()).getFormattersForResource(cms, res);
2059        } catch (CmsException e) {
2060            LOG.error(e.getLocalizedMessage(), e);
2061            return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
2062        }
2063    }
2064
2065    /**
2066     * Gets the metadata about how this configuration was referenced.
2067     *
2068     * @return the metadata
2069     */
2070    protected ConfigReferenceMeta getMeta() {
2071
2072        return m_configSequence.getMeta();
2073    }
2074
2075    /**
2076     * Internal method for getting the function references.<p>
2077     *
2078     * @return the function references
2079     */
2080    protected List<CmsFunctionReference> internalGetFunctionReferences() {
2081
2082        CmsADEConfigData parentData = parent();
2083        if ((parentData == null)) {
2084            if (m_data.isModuleConfig()) {
2085                return Collections.unmodifiableList(m_data.getFunctionReferences());
2086            } else {
2087                return Lists.newArrayList();
2088            }
2089        } else {
2090            return parentData.internalGetFunctionReferences();
2091
2092        }
2093    }
2094
2095    /**
2096     * Helper method for getting the list of resource types.<p>
2097     *
2098     * @param filterDisabled true if disabled types should be filtered from the result
2099     *
2100     * @return the list of resource types
2101     */
2102    protected List<CmsResourceTypeConfig> internalGetResourceTypes(boolean filterDisabled) {
2103
2104        CmsADEConfigData parentData = parent();
2105        List<CmsResourceTypeConfig> parentResourceTypes = null;
2106        if (parentData == null) {
2107            parentResourceTypes = Lists.newArrayList();
2108        } else {
2109            parentResourceTypes = Lists.newArrayList();
2110            for (CmsResourceTypeConfig typeConfig : parentData.internalGetResourceTypes(false)) {
2111                CmsResourceTypeConfig copiedType = typeConfig.copy(
2112                    m_data.isDiscardInheritedTypes() && !getMeta().isSkipRemovals());
2113                parentResourceTypes.add(copiedType);
2114            }
2115        }
2116        String template = getMeta().getTemplate();
2117        List<CmsResourceTypeConfig> result = combineConfigurationElements(
2118            parentResourceTypes,
2119            m_data.getOwnResourceTypes().stream().map(type -> type.markWithTemplate(template)).collect(
2120                Collectors.toList()),
2121            true);
2122        if (m_data.isCreateContentsLocally()) {
2123            CmsContentFolderOption contentFolderOption = getContentFolderOption();
2124            CmsUUID folderId = null;
2125            if (contentFolderOption != null) {
2126                folderId = contentFolderOption.getFolderId();
2127            }
2128            String basePath = CmsStringUtil.joinPaths(m_data.getBasePath(), CmsADEManager.CONTENT_FOLDER_NAME);
2129            if (folderId != null) {
2130                try {
2131                    CmsResource resource = getCms().readResource(folderId, CmsResourceFilter.ALL);
2132                    if (!resource.isFolder()) {
2133                        LOG.error(
2134                            "Resource configured as content folder isn't a folder: "
2135                                + resource.getRootPath()
2136                                + " (context: "
2137                                + getBasePath()
2138                                + ")");
2139                    } else {
2140                        basePath = CmsFileUtil.removeTrailingSeparator(resource.getRootPath());
2141                    }
2142                } catch (Exception e) {
2143                    LOG.error(e.getLocalizedMessage(), e);
2144                }
2145            }
2146
2147            for (CmsResourceTypeConfig typeConfig : result) {
2148                typeConfig.updateBasePath(basePath);
2149            }
2150        }
2151        if (filterDisabled) {
2152            Iterator<CmsResourceTypeConfig> iter = result.iterator();
2153            while (iter.hasNext()) {
2154                CmsResourceTypeConfig typeConfig = iter.next();
2155                if (typeConfig.isDisabled()) {
2156                    iter.remove();
2157                }
2158            }
2159        }
2160        if (getTypeOrderingMode() == CmsTypeOrderingMode.byDisplayOrder) {
2161            Collections.sort(result, (a, b) -> Integer.compare(a.getOrder(), b.getOrder()));
2162        }
2163        return result;
2164    }
2165
2166    /**
2167     * Merges two lists of detail pages, one from a parent configuration and one from a child configuration.<p>
2168     *
2169     * @param parentDetailPages the parent's detail pages
2170     * @param ownDetailPages the child's detail pages
2171     *
2172     * @return the merged detail pages
2173     */
2174    protected List<CmsDetailPageInfo> mergeDetailPages(
2175        List<CmsDetailPageInfo> parentDetailPages,
2176        List<CmsDetailPageInfo> ownDetailPages) {
2177
2178        List<CmsDetailPageInfo> parentDetailPageCopies = Lists.newArrayList();
2179        for (CmsDetailPageInfo info : parentDetailPages) {
2180            parentDetailPageCopies.add(info.copyAsInherited());
2181        }
2182
2183        List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>();
2184        Map<String, List<CmsDetailPageInfo>> resultDetailPageMap = Maps.newHashMap();
2185        Map<String, List<CmsDetailPageInfo>> parentPagesGroupedByType = getDetailPagesMap(parentDetailPageCopies);
2186        Map<String, List<CmsDetailPageInfo>> childPagesGroupedByType = getDetailPagesMap(ownDetailPages);
2187        Set<String> allTypes = new HashSet<>();
2188        allTypes.addAll(parentPagesGroupedByType.keySet());
2189        allTypes.addAll(childPagesGroupedByType.keySet());
2190        for (String type : allTypes) {
2191
2192            List<CmsDetailPageInfo> parentPages = parentPagesGroupedByType.get(type);
2193            List<CmsDetailPageInfo> childPages = childPagesGroupedByType.get(type);
2194            List<CmsDetailPageInfo> merged = mergeDetailPagesForType(parentPages, childPages);
2195            resultDetailPageMap.put(type, merged);
2196        }
2197        result = new ArrayList<CmsDetailPageInfo>();
2198        for (List<CmsDetailPageInfo> pages : resultDetailPageMap.values()) {
2199            result.addAll(pages);
2200        }
2201        return result;
2202    }
2203
2204    /**
2205     * Helper method to correct paths in detail page beans if the corresponding resources have been moved.<p>
2206     *
2207     * @param detailPages the original list of detail pages
2208     *
2209     * @return the corrected list of detail pages
2210     */
2211    protected List<CmsDetailPageInfo> updateUris(List<CmsDetailPageInfo> detailPages) {
2212
2213        List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>();
2214        for (CmsDetailPageInfo page : detailPages) {
2215            CmsUUID structureId = page.getId();
2216            try {
2217                String rootPath = OpenCms.getADEManager().getRootPath(
2218                    structureId,
2219                    getCms().getRequestContext().getCurrentProject().isOnlineProject());
2220                String iconClasses;
2221                if (page.getType().startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)) {
2222                    iconClasses = CmsIconUtil.getIconClasses(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION, null, false);
2223                } else {
2224                    iconClasses = CmsIconUtil.getIconClasses(page.getType(), null, false);
2225                }
2226                CmsDetailPageInfo correctedPage = new CmsDetailPageInfo(
2227                    structureId,
2228                    rootPath,
2229                    page.getType(),
2230                    page.getQualifier(),
2231                    page.getFolders(),
2232                    iconClasses);
2233                result.add(page.isInherited() ? correctedPage.copyAsInherited() : correctedPage);
2234            } catch (CmsException e) {
2235                LOG.warn(e.getLocalizedMessage(), e);
2236            }
2237        }
2238        return result;
2239    }
2240
2241    /**
2242     * Gets a multimap of active formatters for which a formatter key is defined, with the formatter keys as map keys.
2243     *
2244     * @return the map of active formatters by key
2245     */
2246    private Multimap<String, I_CmsFormatterBean> getActiveFormattersByKey() {
2247
2248        if (m_activeFormattersByKey == null) {
2249            ArrayListMultimap<String, I_CmsFormatterBean> activeFormattersByKey = ArrayListMultimap.create();
2250            for (I_CmsFormatterBean formatter : getActiveFormatters().values()) {
2251                for (String key : formatter.getAllKeys()) {
2252                    activeFormattersByKey.put(key, formatter);
2253                }
2254            }
2255            m_activeFormattersByKey = activeFormattersByKey;
2256        }
2257        return m_activeFormattersByKey;
2258    }
2259
2260    /**
2261     * Gets a formatter with the given key from a multimap, and warns if there are multiple values
2262     * for the key.
2263     *
2264     * @param formatterMap the formatter multimap
2265     * @param name the formatter key
2266     * @param noWarn if true, disables warnings
2267     * @return the formatter for the key (null if none are found, the first one if multiple are found)
2268     */
2269    private I_CmsFormatterBean getFormatterAndWarnIfAmbiguous(
2270        Multimap<String, I_CmsFormatterBean> formatterMap,
2271        String name,
2272        boolean noWarn) {
2273
2274        I_CmsFormatterBean result;
2275        result = null;
2276        Collection<I_CmsFormatterBean> activeForKey = formatterMap.get(name);
2277        if (activeForKey.size() > 0) {
2278            if (activeForKey.size() > 1) {
2279                if (!noWarn) {
2280                    String labels = ""
2281                        + activeForKey.stream().map(this::getFormatterLabel).collect(Collectors.toList());
2282                    String message = "Ambiguous formatter for key '"
2283                        + name
2284                        + "' at '"
2285                        + getBasePath()
2286                        + "': found "
2287                        + labels;
2288                    LOG.warn(message);
2289                    OpenCmsServlet.withRequestCache(
2290                        rc -> rc.addLog(REQUEST_LOG_CHANNEL, "warn", REQ_LOG_PREFIX + message));
2291                }
2292            }
2293            result = activeForKey.iterator().next();
2294        }
2295        return result;
2296    }
2297
2298    /**
2299     * Gets a user-friendly formatter label to use for logging.
2300     *
2301     * @param formatter a formatter bean
2302     * @return the formatter label for the log
2303     */
2304    private String getFormatterLabel(I_CmsFormatterBean formatter) {
2305
2306        return formatter.getLocation() != null ? formatter.getLocation() : formatter.getId();
2307    }
2308
2309    /**
2310     * Gets formatters by JSP id.
2311     *
2312     * @return the multimap from JSP id to formatter beans
2313     */
2314    private Multimap<CmsUUID, I_CmsFormatterBean> getFormattersByJspId() {
2315
2316        if (m_formattersByJspId == null) {
2317            ArrayListMultimap<CmsUUID, I_CmsFormatterBean> formattersByJspId = ArrayListMultimap.create();
2318            for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) {
2319                formattersByJspId.put(formatter.getJspStructureId(), formatter);
2320            }
2321            m_formattersByJspId = formattersByJspId;
2322        }
2323        return m_formattersByJspId;
2324    }
2325
2326    /**
2327     * Gets a multimap of the formatters for which a formatter key is defined, with the formatter keys as map keys.
2328     *
2329     * @return the map of formatters by key
2330     */
2331    private Multimap<String, I_CmsFormatterBean> getFormattersByKey() {
2332
2333        if (m_formattersByKey == null) {
2334            ArrayListMultimap<String, I_CmsFormatterBean> formattersByKey = ArrayListMultimap.create();
2335            for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) {
2336                for (String key : formatter.getAllKeys()) {
2337                    formattersByKey.put(key, formatter);
2338                }
2339            }
2340            m_formattersByKey = formattersByKey;
2341        }
2342        return m_formattersByKey;
2343    }
2344
2345    /**
2346     * Merges detail pages for a specific resource type from a parent and child sitemap.
2347     *
2348     * @param parentPages the detail pages from the parent sitemap
2349     * @param childPages the detail pages from the child sitemap
2350     * @return the merged detail pages
2351     */
2352    private List<CmsDetailPageInfo> mergeDetailPagesForType(
2353        List<CmsDetailPageInfo> parentPages,
2354        List<CmsDetailPageInfo> childPages) {
2355
2356        List<CmsDetailPageInfo> merged = null;
2357        if ((parentPages != null) && (childPages != null)) {
2358            if (childPages.stream().anyMatch(page -> page.getQualifier() == null)) {
2359                // If the child detail pages contain one with an unqualified type, they completely override the parent detail pages.
2360                merged = childPages;
2361            } else {
2362                // Otherwise, all child pages with a specific merge key override all parent pages with the same merge key
2363                Map<String, List<CmsDetailPageInfo>> pagesGroupedByMergeKey = new HashMap<>();
2364                for (List<CmsDetailPageInfo> pages : Arrays.asList(parentPages, childPages)) {
2365                    pagesGroupedByMergeKey.putAll(
2366                        pages.stream().collect(Collectors.groupingBy(page -> page.getMergeKey())));
2367                }
2368                // combine page lists for all merge keys into a single page list
2369                merged = pagesGroupedByMergeKey.entrySet().stream().flatMap(entry -> entry.getValue().stream()).collect(
2370                    Collectors.toList());
2371            }
2372        } else if (parentPages != null) {
2373            merged = parentPages;
2374        } else if (childPages != null) {
2375            merged = childPages;
2376        } else {
2377            merged = new ArrayList<>();
2378        }
2379        return merged;
2380    }
2381
2382}