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