001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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.CmsADEConfigData.DetailInfo;
031import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReference;
032import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceInstance;
033import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceMeta;
034import org.opencms.ade.configuration.plugins.CmsSitePlugin;
035import org.opencms.ade.detailpage.CmsDetailPageInfo;
036import org.opencms.file.CmsObject;
037import org.opencms.file.CmsResource;
038import org.opencms.file.CmsResourceFilter;
039import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
040import org.opencms.main.CmsException;
041import org.opencms.main.CmsLog;
042import org.opencms.main.OpenCms;
043import org.opencms.util.CmsStringUtil;
044import org.opencms.util.CmsUUID;
045
046import java.util.ArrayList;
047import java.util.Collections;
048import java.util.HashMap;
049import java.util.HashSet;
050import java.util.List;
051import java.util.Map;
052import java.util.Set;
053import java.util.concurrent.ConcurrentHashMap;
054import java.util.concurrent.TimeUnit;
055import java.util.stream.Collectors;
056
057import org.apache.commons.logging.Log;
058
059import com.google.common.base.Supplier;
060import com.google.common.base.Suppliers;
061import com.google.common.cache.CacheBuilder;
062import com.google.common.collect.Lists;
063import com.google.common.collect.Maps;
064
065/**
066 * An immutable object which represents the complete ADE configuration (sitemap and module configurations)
067 * at a certain instant in time.<p>
068 */
069public class CmsADEConfigCacheState {
070
071    /** The logger instance for this class. */
072    private static final Log LOG = CmsLog.getLog(CmsADEConfigCacheState.class);
073
074    /** The CMS context used for VFS operations. */
075    private CmsObject m_cms;
076
077    /** Cached set of names of content types anywhere in the configuration. */
078    private volatile Set<String> m_contentTypes;
079
080    /** Cache for detail page lists. */
081    private Map<String, List<String>> m_detailPageCache;
082
083    /** Memoized supplier for the cached detail page ids. */
084    private Supplier<Set<CmsUUID>> m_detailPageIdCache;
085
086    /** Cached detail page types. */
087    private volatile Set<String> m_detailPageTypes;
088
089    /** The available element views. */
090    private Map<CmsUUID, CmsElementView> m_elementViews;
091
092    /** The cached content types for folders. */
093    private Map<String, String> m_folderTypes = new HashMap<String, String>();
094
095    /** The merged configuration from all the modules. */
096    private CmsADEConfigDataInternal m_moduleConfiguration;
097
098    /** The list of module configurations. */
099    private List<CmsADEConfigDataInternal> m_moduleConfigurations;
100
101    /** The map of sitemap configurations by structure id. */
102    private Map<CmsUUID, CmsADEConfigDataInternal> m_siteConfigurations = new HashMap<CmsUUID, CmsADEConfigDataInternal>();
103
104    /** The configurations from the sitemap / VFS. */
105    private Map<String, CmsADEConfigDataInternal> m_siteConfigurationsByPath = new HashMap<String, CmsADEConfigDataInternal>();
106
107    /** The sitemap attribute editor configurations. */
108    private Map<CmsUUID, CmsSitemapAttributeEditorConfiguration> m_sitemapAttributeEditorConfigurations;
109
110    /** Site plugins. */
111    private Map<CmsUUID, CmsSitePlugin> m_sitePlugins;
112    
113    /** Cache for sitemap attribute overview maps. */
114    private ConcurrentHashMap<String, Map<String, String>> m_attributeValuesByPathCache = new ConcurrentHashMap<>();
115
116    /** Cached list of subsites to be included in the site selector. */
117    private volatile List<String> m_subsitesForSiteSelector;
118
119    /**
120     * Creates a new configuration cache state.<p>
121     *
122     * @param cms the CMS context to use
123     * @param siteConfigurations the map of sitemap configuration beans by structure id
124     * @param moduleConfigs the complete list of module configurations
125     * @param elementViews the available element views
126     * @param sitePlugins the map of sitemap plugins
127     * @param attributeEditorConfigurations the attribute editor configurations
128     */
129    public CmsADEConfigCacheState(
130        CmsObject cms,
131        Map<CmsUUID, CmsADEConfigDataInternal> siteConfigurations,
132        List<CmsADEConfigDataInternal> moduleConfigs,
133        Map<CmsUUID, CmsElementView> elementViews,
134        Map<CmsUUID, CmsSitePlugin> sitePlugins,
135        Map<CmsUUID, CmsSitemapAttributeEditorConfiguration> attributeEditorConfigurations) {
136
137        m_cms = cms;
138        m_siteConfigurations = siteConfigurations;
139        m_moduleConfigurations = moduleConfigs;
140        m_elementViews = elementViews;
141        m_sitePlugins = sitePlugins;
142        m_sitemapAttributeEditorConfigurations = attributeEditorConfigurations;
143        for (CmsADEConfigDataInternal data : siteConfigurations.values()) {
144            if (data.getBasePath() != null) {
145                m_siteConfigurationsByPath.put(data.getBasePath(), data);
146            } else {
147                LOG.info("Empty base path for sitemap configuration: " + data.getResource().getRootPath());
148            }
149        }
150        m_moduleConfiguration = mergeConfigurations(moduleConfigs);
151        try {
152            m_folderTypes = computeFolderTypes();
153        } catch (Exception e) {
154            m_folderTypes = Maps.newHashMap();
155            LOG.error(e.getLocalizedMessage(), e);
156        }
157        CacheBuilder<?, ?> detailPageCacheBuilder = CacheBuilder.newBuilder().concurrencyLevel(8).expireAfterWrite(
158            30,
159            TimeUnit.SECONDS);
160        m_detailPageCache = (Map<String, List<String>>)(detailPageCacheBuilder.build().asMap());
161        m_detailPageIdCache = Suppliers.memoize(this::collectDetailPageIds);
162
163    }
164
165    /**
166     * Creates an empty ADE configuration cache state.<p>
167     *
168     * @param cms the CMS context
169     * @return the empty configuration cache state
170     */
171    public static CmsADEConfigCacheState emptyState(CmsObject cms) {
172
173        return new CmsADEConfigCacheState(
174            cms,
175            Collections.<CmsUUID, CmsADEConfigDataInternal> emptyMap(),
176            Collections.<CmsADEConfigDataInternal> emptyList(),
177            Collections.<CmsUUID, CmsElementView> emptyMap(),
178            Collections.emptyMap(),
179            Collections.emptyMap());
180    }
181
182    /**
183     * Computes the map from folder paths to content types for this ADE configuration state.<p>
184     *
185     * @return the map of content types by folder root paths
186     *
187     * @throws CmsException if something goes wrong
188     */
189    public Map<String, String> computeFolderTypes() throws CmsException {
190
191        Map<String, String> folderTypes = Maps.newHashMap();
192        // do this first, since folder types from modules should be overwritten by folder types from sitemaps
193        if (m_moduleConfiguration != null) {
194            folderTypes.putAll(wrap(m_moduleConfiguration).getFolderTypes());
195        }
196
197        List<CmsADEConfigDataInternal> configDataObjects = new ArrayList<CmsADEConfigDataInternal>(
198            m_siteConfigurationsByPath.values());
199        for (CmsADEConfigDataInternal configData : configDataObjects) {
200            folderTypes.putAll(wrap(configData).getFolderTypes());
201        }
202        return folderTypes;
203    }
204
205    /**
206     * Creates a new object which represents the changed configuration state given some updates, without
207     * changing the current configuration state (this object instance).
208     *
209     * @param sitemapUpdates a map containing changed sitemap configurations indexed by structure id (the map values are null if the corresponding sitemap configuration is not valid or could not be found)
210     * @param moduleUpdates the list of *all* module configurations, or null if no module configuration update is needed
211     * @param elementViewUpdates the updated element views, or null if no update needed
212     * @param sitePluginUpdates the new map of site plugins, or null if no update needed
213     * @param attributeEditorConfigurations the sitemap attribute editor configurations
214     *
215     * @return the new configuration state
216     */
217    public CmsADEConfigCacheState createUpdatedCopy(
218        Map<CmsUUID, CmsADEConfigDataInternal> sitemapUpdates,
219        List<CmsADEConfigDataInternal> moduleUpdates,
220        Map<CmsUUID, CmsElementView> elementViewUpdates,
221        Map<CmsUUID, CmsSitePlugin> sitePluginUpdates,
222        Map<CmsUUID, CmsSitemapAttributeEditorConfiguration> attributeEditorConfigurations) {
223
224        Map<CmsUUID, CmsADEConfigDataInternal> newSitemapConfigs = Maps.newHashMap(m_siteConfigurations);
225        if (sitemapUpdates != null) {
226            for (Map.Entry<CmsUUID, CmsADEConfigDataInternal> entry : sitemapUpdates.entrySet()) {
227                CmsUUID key = entry.getKey();
228                CmsADEConfigDataInternal value = entry.getValue();
229                if (value != null) {
230                    newSitemapConfigs.put(key, value);
231                } else {
232                    newSitemapConfigs.remove(key);
233                }
234            }
235        }
236        List<CmsADEConfigDataInternal> newModuleConfigs = m_moduleConfigurations;
237        if (moduleUpdates != null) {
238            newModuleConfigs = moduleUpdates;
239        }
240        Map<CmsUUID, CmsElementView> newElementViews = m_elementViews;
241        if (elementViewUpdates != null) {
242            newElementViews = elementViewUpdates;
243        }
244
245        Map<CmsUUID, CmsSitePlugin> newSitePlugins = m_sitePlugins;
246        if (sitePluginUpdates != null) {
247            newSitePlugins = sitePluginUpdates;
248        }
249
250        Map<CmsUUID, CmsSitemapAttributeEditorConfiguration> newAttributeEditorConfigs = m_sitemapAttributeEditorConfigurations;
251        if (attributeEditorConfigurations != null) {
252            newAttributeEditorConfigs = attributeEditorConfigurations;
253        }
254
255        return new CmsADEConfigCacheState(
256            m_cms,
257            newSitemapConfigs,
258            newModuleConfigs,
259            newElementViews,
260            newSitePlugins,
261            newAttributeEditorConfigs);
262    }
263
264    /**
265     * Gets the sitemap attribute editor configuration with the given id (or null, if there isn't one).
266     *
267     * @param id the structure id of an attribute editor configuration
268     * @return the attribute editor configuration for the id
269     */
270    public CmsSitemapAttributeEditorConfiguration getAttributeEditorConfiguration(CmsUUID id) {
271
272        return m_sitemapAttributeEditorConfigurations.get(id);
273    }
274
275    /**
276     * Gets a map of the values of a specific sitemap attribute for each subsite.
277     *
278     * <p>The keys of the map are the root paths of the subsite, and the values are the attribute values.
279     *
280     * @param attribute the attribute we want to get
281     *
282     * @return the map of attribute values
283     */
284    public Map<String, String> getAttributeValuesByPath(String attribute) {
285
286        return m_attributeValuesByPathCache.computeIfAbsent(attribute, attr -> {
287            Map<String, String> result = new HashMap<>();
288            for (String path : m_siteConfigurationsByPath.keySet()) {
289                CmsADEConfigData config = lookupConfiguration(path);
290                String value = config.getAttribute(attr, null);
291                if (value != null) {
292                    result.put(path, value);
293                }
294            }
295            return Collections.unmodifiableMap(result);
296        });
297    }
298
299    /**
300     * Gets the set of content types configured anywhere in sitemap configurations.
301     *
302     * @return the set of content types
303     */
304    public Set<String> getContentTypes() {
305
306        if (m_contentTypes == null) {
307            Set<String> contentTypes = new HashSet<>();
308            for (CmsADEConfigDataInternal config : m_siteConfigurations.values()) {
309                for (CmsResourceTypeConfig typeConfig : config.getOwnResourceTypes()) {
310                    contentTypes.add(typeConfig.getTypeName());
311                }
312            }
313            for (CmsResourceTypeConfig typeConfig : m_moduleConfiguration.getOwnResourceTypes()) {
314                contentTypes.add(typeConfig.getTypeName());
315            }
316            m_contentTypes = Collections.unmodifiableSet(contentTypes);
317        }
318        return m_contentTypes;
319    }
320
321    /**
322     * Gets the detail page information for everything.<p>
323     *
324     * @param cms the current CMS context
325     * @return the list containing all detail information
326     */
327    public List<DetailInfo> getDetailInfosForSubsites(CmsObject cms) {
328
329        List<DetailInfo> result = Lists.newArrayList();
330        for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) {
331            List<DetailInfo> infosForSubsite = wrap(configData).getDetailInfos(cms);
332            result.addAll(infosForSubsite);
333        }
334        return result;
335    }
336
337    /**
338     * Gets the set of type names for which detail pages are configured in any sitemap configuration.<p>
339     *
340     * @return the set of type names with configured detail pages
341     */
342    public Set<String> getDetailPageTypes() {
343
344        if (m_detailPageTypes != null) {
345            return m_detailPageTypes;
346        }
347        Set<String> result = new HashSet<String>();
348        for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) {
349            List<CmsDetailPageInfo> detailPageInfos = configData.getOwnDetailPages();
350            for (CmsDetailPageInfo info : detailPageInfos) {
351                result.add(info.getType());
352            }
353        }
354        m_detailPageTypes = result;
355        return result;
356    }
357
358    /**
359     * Returns the element views.<p>
360     *
361     * @return the element views
362     */
363    public Map<CmsUUID, CmsElementView> getElementViews() {
364
365        return Collections.unmodifiableMap(m_elementViews);
366    }
367
368    /**
369     * Gets the map of folder types.<p>
370     *
371     * @return the map of folder types
372     */
373    public Map<String, String> getFolderTypes() {
374
375        return Collections.unmodifiableMap(m_folderTypes);
376    }
377
378    /**
379     * Helper method to retrieve the parent folder type or <code>null</code> if none available.<p>
380     *
381     * @param rootPath the path of a resource
382     * @return the parent folder content type
383     */
384    public String getParentFolderType(String rootPath) {
385
386        String parent = CmsResource.getParentFolder(rootPath);
387        if (parent == null) {
388            return null;
389        }
390        String type = m_folderTypes.get(parent);
391        // type may be null
392        return type;
393    }
394
395    /**
396     * Gets the raw detail page information, with no existence checks or path corrections.
397     *
398     * @return the detail page information
399     */
400    public List<CmsDetailPageInfo> getRawDetailPages() {
401
402        List<CmsDetailPageInfo> result = new ArrayList<>();
403        for (CmsADEConfigDataInternal config : m_siteConfigurationsByPath.values()) {
404            result.addAll(config.getOwnDetailPages());
405        }
406        return result;
407    }
408
409    /**
410     * Returns the root paths to all configured sites and sub sites.<p>
411     *
412     * @return the root paths to all configured sites and sub sites
413     */
414    public Set<String> getSiteConfigurationPaths() {
415
416        return m_siteConfigurationsByPath.keySet();
417    }
418
419    /**
420     * The map of site plugins, by structure id.
421     *
422     * @return the map of site plugins
423     */
424    public Map<CmsUUID, CmsSitePlugin> getSitePlugins() {
425
426        return m_sitePlugins;
427    }
428
429    /**
430     * Gets subsites to be included in the site selector.
431     *
432     * @return the list of root paths of subsites that should be included in the site selector
433     */
434    public List<String> getSubsitesForSiteSelector() {
435
436        if (m_subsitesForSiteSelector == null) {
437            List<String> paths = m_siteConfigurations.values().stream().filter(
438                conf -> conf.isIncludeInSiteSelector()).map(conf -> conf.getBasePath()).collect(Collectors.toList());
439            m_subsitesForSiteSelector = Collections.unmodifiableList(paths);
440        }
441        return m_subsitesForSiteSelector;
442    }
443
444    /**
445     * Looks up the sitemap configuration for a root path.<p>
446     * @param rootPath the root path for which to look up the configuration
447     *
448     * @return the sitemap configuration for the given root path
449     */
450    public CmsADEConfigData lookupConfiguration(String rootPath) {
451
452        CmsADEConfigDataInternal internalSiteConfig = getSiteConfigData(rootPath);
453        CmsADEConfigData result;
454        if (internalSiteConfig == null) {
455            result = wrap(m_moduleConfiguration);
456        } else {
457            result = wrap(internalSiteConfig);
458        }
459        return result;
460    }
461
462    /**
463     * Gets all detail page info beans which are defined anywhere in the configuration.<p>
464     *
465     * @return the list of detail page info beans
466     */
467    protected List<CmsDetailPageInfo> getAllDetailPages() {
468
469        List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>();
470        for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) {
471            result.addAll(wrap(configData).getAllDetailPages(true));
472        }
473        return result;
474    }
475
476    /**
477     * Gets the CMS context used for VFS operations.<p>
478     *
479     * @return the CMS context used for VFS operations
480     */
481    protected CmsObject getCms() {
482
483        return m_cms;
484    }
485
486    /**
487     * Gets all the detail pages for a given type.<p>
488     *
489     * @param type the name of the type
490     *
491     * @return the detail pages for that type
492     */
493    protected List<String> getDetailPages(String type) {
494
495        List<String> result = m_detailPageCache.get(type);
496        if (result == null) {
497            result = new ArrayList<>();
498            for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) {
499                for (CmsDetailPageInfo pageInfo : wrap(configData).getDetailPagesForType(type)) {
500                    result.add(pageInfo.getUri());
501                }
502            }
503            m_detailPageCache.put(type, result);
504        }
505        return Collections.unmodifiableList(result);
506    }
507
508    /**
509     * Gets the merged module configuration.<p>
510     * @return the merged module configuration instance
511     */
512    protected CmsADEConfigData getModuleConfiguration() {
513
514        return wrap(m_moduleConfiguration);
515    }
516
517    /**
518     * Helper method for getting the best matching sitemap configuration object for a given root path, ignoring the module
519     * configuration.<p>
520     *
521     * For example, if there are configurations available for the paths /a, /a/b/c, /a/b/x and /a/b/c/d/e, then
522     * the method will return the configuration object for /a/b/c when passed the path /a/b/c/d.
523     *
524     * If no configuration data is found for the path, null will be returned.<p>
525     *
526     * @param path a root path
527     * @return the configuration data for the given path, or null if none was found
528     */
529    protected CmsADEConfigDataInternal getSiteConfigData(String path) {
530
531        if (path == null) {
532            return null;
533        }
534        List<String> prefixes = getSiteConfigPaths(path);
535        if (prefixes.size() == 0) {
536            return null;
537        }
538        // for any two prefixes of a string, one is a prefix of the other. so the alphabetically last
539        // prefix is the longest prefix of all.
540        return m_siteConfigurationsByPath.get(prefixes.get(prefixes.size() - 1));
541    }
542
543    /**
544     * Finds the paths of sitemap configuration base paths above a given path.<p>
545     *
546     * @param path the path for which to find the base paths of all valid sitemap configurations
547     *
548     * @return the list of base paths
549     */
550    protected List<String> getSiteConfigPaths(String path) {
551
552        String normalizedPath = CmsStringUtil.joinPaths("/", path, "/");
553        List<String> prefixes = new ArrayList<String>();
554
555        List<String> parents = new ArrayList<String>();
556        String currentPath = normalizedPath;
557        while (currentPath != null) {
558            parents.add(currentPath);
559            currentPath = CmsResource.getParentFolder(currentPath);
560        }
561
562        for (String parent : parents) {
563            if (m_siteConfigurationsByPath.containsKey(parent)) {
564                prefixes.add(parent);
565            }
566        }
567        Collections.sort(prefixes);
568        return prefixes;
569    }
570
571    /**
572     * Checks whether the given resource is configured as a detail page.<p>
573     *
574     * @param cms the current CMS context
575     * @param resource the resource to test
576     *
577     * @return true if the resource is configured as a detail page
578     */
579    protected boolean isDetailPage(CmsObject cms, CmsResource resource) {
580
581        if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
582            Set<CmsUUID> detailPageIds = m_detailPageIdCache.get();
583            if (detailPageIds.contains(resource.getStructureId())) {
584                // page may have been created/replaced after the detail page id cache was generated,
585                // so we don't just return false if it doesn't contain the id.
586                // instead we check the parent folder too in the next step.
587                return true;
588            }
589
590            try {
591                CmsResource parent = getCms().readResource(
592                    CmsResource.getParentFolder(resource.getRootPath()),
593                    CmsResourceFilter.ALL);
594                return detailPageIds.contains(parent.getStructureId());
595
596            } catch (Exception e) {
597                LOG.info(e.getLocalizedMessage(), e);
598                return false;
599            }
600        } else if (resource.isFolder()) {
601            return m_detailPageIdCache.get().contains(resource.getStructureId());
602        } else {
603            return false;
604        }
605    }
606
607    /**
608     * Merges a list of multiple configuration objects into a single configuration object.<p>
609     *
610     * @param configurations the list of configuration objects.<p>
611     *
612     * @return the merged configuration object
613     */
614    protected CmsADEConfigDataInternal mergeConfigurations(List<CmsADEConfigDataInternal> configurations) {
615
616        if (configurations.isEmpty()) {
617            return new CmsADEConfigDataInternal(null);
618        }
619        for (int i = 0; i < (configurations.size() - 1); i++) {
620            configurations.get(i + 1).mergeParent(configurations.get(i));
621        }
622        CmsADEConfigDataInternal result = configurations.get(configurations.size() - 1);
623        result.processModuleOrdering();
624        return result;
625    }
626
627    /**
628     * Internal method for collecting structure ids of all configured detail pages and their parent folders.
629     *
630     * @return the structure ids of configured detail pages and their parent folders
631     */
632    private Set<CmsUUID> collectDetailPageIds() {
633
634        List<CmsDetailPageInfo> allDetailPages = new ArrayList<CmsDetailPageInfo>();
635        // First collect all detail page infos
636        for (CmsADEConfigDataInternal configData : m_siteConfigurationsByPath.values()) {
637            List<CmsDetailPageInfo> detailPageInfos = configData.getOwnDetailPages();
638            allDetailPages.addAll(detailPageInfos);
639        }
640        Set<CmsUUID> detailPageOrDetailPageFolderIds = new HashSet<>();
641        for (CmsDetailPageInfo detailPageInfo : allDetailPages) {
642            try {
643                CmsResource detailPageRes = getCms().readResource(detailPageInfo.getId(), CmsResourceFilter.ALL);
644                detailPageOrDetailPageFolderIds.add(detailPageInfo.getId());
645                if (detailPageRes.isFile()) {
646                    CmsResource parent = getCms().readParentFolder(detailPageInfo.getId());
647                    detailPageOrDetailPageFolderIds.add(parent.getStructureId());
648                } else {
649                    CmsResource defaultfile = getCms().readDefaultFile("" + detailPageInfo.getId());
650                    if (defaultfile != null) {
651                        detailPageOrDetailPageFolderIds.add(defaultfile.getStructureId());
652                    }
653                }
654            } catch (Exception e) {
655                LOG.info(e.getLocalizedMessage(), e);
656            }
657        }
658        return Collections.unmodifiableSet(detailPageOrDetailPageFolderIds);
659    }
660
661    /**
662     * For a given master configuration, lists all directly and indirectly referenced master configurations, in sitemap config inheritance order (i.e. referenced master configurations preceding the
663     * configurations from which they are referenced).
664     *
665     * @param result the list to append the results to
666     * @param current the configuration reference to start with
667     * @param seen the set of structure ids of sitemap configurations already visited
668     */
669    private void fillMasterConfigurations(
670        List<ConfigReferenceInstance> result,
671        ConfigReferenceInstance current,
672        Set<CmsUUID> seen) {
673
674        CmsUUID currentId = current.getConfig().getResource().getStructureId();
675        if (seen.contains(currentId)) {
676            LOG.warn("Loop in sitemap configuration references, target = " + current.getConfig().getBasePath());
677            return;
678        }
679        seen.add(currentId);
680        // Recursively add the referenced master configurations before adding the current configuration in the end
681        for (ConfigReference configRef : current.getConfig().getMasterConfigs()) {
682            CmsADEConfigDataInternal config = m_siteConfigurations.get(configRef.getId());
683            if (config != null) {
684                ConfigReferenceMeta combinedMeta = current.getMeta().combine(configRef.getMeta());
685                ConfigReferenceInstance combinedRef = new ConfigReferenceInstance(config, combinedMeta);
686                fillMasterConfigurations(result, combinedRef, seen);
687            } else {
688                LOG.warn(
689                    "Master configuration with id "
690                        + configRef.getId()
691                        + " not found, referenced by "
692                        + current.getConfig().getResource().getRootPath());
693            }
694        }
695        result.add(current);
696        seen.remove(currentId);
697    }
698
699    /**
700     * Wraps the internal config data into a bean which manages the lookup of inherited configurations.<p>
701     *
702     * @param data the config data to wrap
703     *
704     * @return the wrapper object
705     */
706    private CmsADEConfigData wrap(CmsADEConfigDataInternal data) {
707
708        String path = data.getBasePath();
709        List<ConfigReferenceInstance> configList = Lists.newArrayList();
710        configList.add(new ConfigReferenceInstance(m_moduleConfiguration));
711        if (path != null) {
712            List<String> siteConfigPaths = getSiteConfigPaths(path);
713            for (String siteConfigPath : siteConfigPaths) {
714                CmsADEConfigDataInternal currentConfig = m_siteConfigurationsByPath.get(siteConfigPath);
715                fillMasterConfigurations(configList, new ConfigReferenceInstance(currentConfig), new HashSet<>());
716            }
717        }
718        CmsADEConfigData result = new CmsADEConfigData(data, this, new CmsADEConfigurationSequence(configList));
719        List<I_CmsSitemapExtraInfo> extraInfo = new ArrayList<>();
720        for (I_CmsSitemapExtraInfoProvider provider : OpenCms.getADEManager().getSitemapExtraInfoProviders()) {
721            extraInfo.add(provider.getExtraInfo(m_cms));
722        }
723        result.m_extraInfo = extraInfo;
724        return result;
725    }
726}