001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (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.formatters.CmsFormatterBeanParser;
031import org.opencms.ade.configuration.formatters.CmsFormatterChangeSet;
032import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCacheState;
033import org.opencms.ade.containerpage.shared.CmsContainer;
034import org.opencms.ade.detailpage.CmsDetailPageInfo;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsResourceFilter;
038import org.opencms.file.types.CmsResourceTypeFolder;
039import org.opencms.file.types.CmsResourceTypeFunctionConfig;
040import org.opencms.file.types.CmsResourceTypeXmlContent;
041import org.opencms.file.types.I_CmsResourceType;
042import org.opencms.gwt.CmsIconUtil;
043import org.opencms.jsp.util.CmsFunctionRenderer;
044import org.opencms.loader.CmsLoaderException;
045import org.opencms.main.CmsException;
046import org.opencms.main.CmsLog;
047import org.opencms.main.OpenCms;
048import org.opencms.util.CmsStringUtil;
049import org.opencms.util.CmsUUID;
050import org.opencms.xml.CmsXmlContentDefinition;
051import org.opencms.xml.containerpage.CmsFormatterConfiguration;
052import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler;
053import org.opencms.xml.containerpage.I_CmsFormatterBean;
054import org.opencms.xml.content.CmsXmlContentFactory;
055import org.opencms.xml.content.CmsXmlContentProperty;
056
057import java.util.ArrayList;
058import java.util.Collection;
059import java.util.Collections;
060import java.util.HashMap;
061import java.util.HashSet;
062import java.util.Iterator;
063import java.util.LinkedHashMap;
064import java.util.List;
065import java.util.Map;
066import java.util.Set;
067
068import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
069import org.apache.commons.logging.Log;
070
071import com.google.common.base.Optional;
072import com.google.common.collect.Lists;
073import com.google.common.collect.Maps;
074import com.google.common.collect.Sets;
075
076/**
077 * A class which represents the accessible configuration data at a given point in a sitemap.<p>
078 */
079public class CmsADEConfigData {
080
081    /**
082     * Bean which contains the detail information for a single sub-sitemap and resource type.<p>
083     *
084     * This includes both  information about the detail page itself, as well as the path of the folder
085     * which is used to store that content type in this subsitemap.<p>
086     *
087     */
088    public class DetailInfo {
089
090        /** The base path of the sitemap configuration where this information originates from. */
091        private String m_basePath;
092
093        /** The information about the detail page info itself. */
094        private CmsDetailPageInfo m_detailPageInfo;
095
096        /** The content folder path. */
097        private String m_folderPath;
098
099        /** The detail type. */
100        private String m_type;
101
102        /**
103         * Creates a new instance.<p>
104         *
105         * @param folderPath the content folder path
106         * @param detailPageInfo the detail page information
107         * @param type the detail type
108         * @param basePath the base path of the sitemap configuration
109         */
110        public DetailInfo(String folderPath, CmsDetailPageInfo detailPageInfo, String type, String basePath) {
111
112            m_folderPath = folderPath;
113            m_detailPageInfo = detailPageInfo;
114            m_type = type;
115            m_basePath = basePath;
116
117        }
118
119        /**
120         * Gets the base path of the sitemap configuration from which this information is coming.<p>
121         *
122         * @return the base path
123         */
124        public String getBasePath() {
125
126            return m_basePath;
127        }
128
129        /**
130         * Gets the detail page information.<p>
131         *
132         * @return the detail page information
133         */
134        public CmsDetailPageInfo getDetailPageInfo() {
135
136            return m_detailPageInfo;
137        }
138
139        /**
140         * Gets the content folder path.<p>
141         *
142         * @return the content folder path
143         */
144        public String getFolderPath() {
145
146            return m_folderPath;
147        }
148
149        /**
150         * Gets the detail type.<p>
151         *
152         * @return the detail type
153         */
154        public String getType() {
155
156            return m_type;
157        }
158
159        /**
160         * Sets the base path.<p>
161         *
162         * @param basePath the new base path value
163         */
164        public void setBasePath(String basePath) {
165
166            m_basePath = basePath;
167        }
168
169        /**
170         * @see java.lang.Object#toString()
171         */
172        @Override
173        public String toString() {
174
175            return ReflectionToStringBuilder.toString(this);
176        }
177    }
178
179    /** The log instance for this class. */
180    private static final Log LOG = CmsLog.getLog(CmsADEConfigData.class);
181
182    /** The wrapped configuration bean containing the actual data. */
183    protected CmsADEConfigDataInternal m_data;
184
185    /** The cache state to which the wrapped configuration bean belongs. */
186    private CmsADEConfigCacheState m_cache;
187
188    /** The configuration sequence (contains the list of all sitemap configuration data beans to be used for inheritance). */
189    private CmsADEConfigurationSequence m_configSequence;
190
191    /**
192     * Creates a new configuration data object, based on an internal configuration data bean and a
193     * configuration cache state.<p>
194     *
195     * @param data the internal configuration data bean
196     * @param cache the configuration cache state
197     * @param configSequence the configuration sequence
198     */
199    public CmsADEConfigData(
200        CmsADEConfigDataInternal data,
201        CmsADEConfigCacheState cache,
202        CmsADEConfigurationSequence configSequence) {
203
204        m_data = data;
205        m_cache = cache;
206        m_configSequence = configSequence;
207    }
208
209    /**
210     * Generic method to merge lists of named configuration objects.<p>
211     *
212     * The lists are merged such that the configuration objects from the child list rise to the front of the result list,
213     * and two configuration objects will be merged themselves if they share the same name.<p>
214     *
215     * For example, if we have two lists of configuration objects:<p>
216     *
217     * parent: A1, B1, C1<p>
218     * child: D2, B2<p>
219     *
220     * then the resulting list will look like:<p>
221     *
222     * D2, B3, A1, C1<p>
223     *
224     * where B3 is the result of merging B1 and B2.<p>
225     *
226     * @param <C> the type of configuration object
227     * @param parentConfigs the parent configurations
228     * @param childConfigs the child configurations
229     * @param preserveDisabled if true, try to merge parents with disabled children instead of discarding them
230     *
231     * @return the merged configuration object list
232     */
233    public static <C extends I_CmsConfigurationObject<C>> List<C> combineConfigurationElements(
234        List<C> parentConfigs,
235        List<C> childConfigs,
236        boolean preserveDisabled) {
237
238        List<C> result = new ArrayList<C>();
239        Map<String, C> map = new LinkedHashMap<String, C>();
240        if (parentConfigs != null) {
241            for (C parent : Lists.reverse(parentConfigs)) {
242                map.put(parent.getKey(), parent);
243            }
244        }
245        if (childConfigs == null) {
246            childConfigs = Collections.emptyList();
247        }
248        for (C child : Lists.reverse(childConfigs)) {
249            String childKey = child.getKey();
250            if (child.isDisabled() && !preserveDisabled) {
251                map.remove(childKey);
252            } else {
253                C parent = map.get(childKey);
254                map.remove(childKey);
255                C newValue;
256                if (parent != null) {
257                    newValue = parent.merge(child);
258                } else {
259                    newValue = child;
260                }
261                map.put(childKey, newValue);
262            }
263        }
264        result = new ArrayList<C>(map.values());
265        Collections.reverse(result);
266        // those multiple "reverse" calls may a bit confusing. They are there because on the one hand we want to keep the
267        // configuration items from one configuration in the same order as they are defined, on the other hand we want
268        // configuration items from a child configuration to rise to the top of the configuration items.
269
270        // so for example, if the parent configuration has items with the keys A,B,C,E
271        // and the child configuration has items  with the keys C,B,D
272        // we want the items of the combined configuration in the order C,B,D,A,E
273
274        return result;
275    }
276
277    /**
278     * Applies the formatter change sets of this and all parent configurations to a map of external (non-schema) formatters.<p>
279     *
280     * @param formatters the external formatter map which will be modified
281     *
282     * @param formatterCacheState the formatter cache state from which new external formatters should be fetched
283     */
284    public void applyAllFormatterChanges(
285        Map<CmsUUID, I_CmsFormatterBean> formatters,
286        CmsFormatterConfigurationCacheState formatterCacheState) {
287
288        for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) {
289            changeSet.applyToFormatters(formatters, formatterCacheState);
290        }
291    }
292
293    /**
294     * Gets the active external (non-schema) formatters for this sub-sitemap.<p>
295     *
296     * @return the map of active external formatters by structure id
297     */
298    public Map<CmsUUID, I_CmsFormatterBean> getActiveFormatters() {
299
300        CmsFormatterConfigurationCacheState cacheState = getCachedFormatters();
301        Map<CmsUUID, I_CmsFormatterBean> result = Maps.newHashMap(cacheState.getAutoEnabledFormatters());
302        applyAllFormatterChanges(result, cacheState);
303        return result;
304    }
305
306    /**
307     * Gets the list of all detail pages.<p>
308     *
309     * @return the list of all detail pages
310     */
311    public List<CmsDetailPageInfo> getAllDetailPages() {
312
313        return getAllDetailPages(true);
314    }
315
316    /**
317     * Gets a list of all detail pages.<p>
318     *
319     * @param update if true, this method will try to correct the root paths in the returned objects if the corresponding resources have been moved
320     *
321     * @return the list of all detail pages
322     */
323    public List<CmsDetailPageInfo> getAllDetailPages(boolean update) {
324
325        CmsADEConfigData parentData = parent();
326        List<CmsDetailPageInfo> parentDetailPages;
327        if (parentData != null) {
328            parentDetailPages = parentData.getAllDetailPages(false);
329        } else {
330            parentDetailPages = Collections.emptyList();
331        }
332        List<CmsDetailPageInfo> result = mergeDetailPages(parentDetailPages, m_data.getOwnDetailPages());
333        if (update) {
334            result = updateUris(result);
335        }
336        return result;
337    }
338
339    /**
340     * Gets the configuration base path.<p>
341     *
342     * For example, if the configuration file is located at /sites/default/.content/.config, the base path is /sites/default.<p>
343     *
344     * @return the base path of the configuration
345     */
346    public String getBasePath() {
347
348        return m_data.getBasePath();
349    }
350
351    /**
352     * Gets the cached formatters.<p>
353     *
354     * @return the cached formatters
355     */
356    public CmsFormatterConfigurationCacheState getCachedFormatters() {
357
358        return OpenCms.getADEManager().getCachedFormatters(
359            getCms().getRequestContext().getCurrentProject().isOnlineProject());
360    }
361
362    /**
363     * Returns the names of the bundles configured as workplace bundles in any module configuration.<p>
364     *
365     * @return the names of the bundles configured as workplace bundles in any module configuration.
366     */
367    public Set<String> getConfiguredWorkplaceBundles() {
368
369        Set<String> result = new HashSet<String>();
370        for (CmsResourceTypeConfig config : internalGetResourceTypes(false)) {
371            String bundlename = config.getConfiguredWorkplaceBundle();
372            if (null != bundlename) {
373                result.add(bundlename);
374            }
375        }
376        return result;
377    }
378
379    /**
380     * Gets the content folder path.<p>
381     *
382     * For example, if the configuration file is located at /sites/default/.content/.config, the content folder path is /sites/default/.content
383     *
384     * @return the content folder path
385     */
386    public String getContentFolderPath() {
387
388        return CmsStringUtil.joinPaths(m_data.getBasePath(), CmsADEManager.CONTENT_FOLDER_NAME);
389
390    }
391
392    /**
393     * Returns a list of the creatable resource types.<p>
394     *
395     * @param cms the CMS context used to check whether the resource types are creatable
396     * @param pageFolderRootPath the root path of the current container page
397     * @return the list of creatable resource type
398     *
399     * @throws CmsException if something goes wrong
400     */
401    public List<CmsResourceTypeConfig> getCreatableTypes(CmsObject cms, String pageFolderRootPath) throws CmsException {
402
403        List<CmsResourceTypeConfig> result = new ArrayList<CmsResourceTypeConfig>();
404        for (CmsResourceTypeConfig typeConfig : getResourceTypes()) {
405            if (typeConfig.checkCreatable(cms, pageFolderRootPath)) {
406                result.add(typeConfig);
407            }
408        }
409        return result;
410    }
411
412    /**
413     * Returns the default detail page.<p>
414     *
415     * @return the default detail page
416     */
417    public CmsDetailPageInfo getDefaultDetailPage() {
418
419        for (CmsDetailPageInfo detailpage : getAllDetailPages(true)) {
420            if (CmsADEManager.DEFAULT_DETAILPAGE_TYPE.equals(detailpage.getType())) {
421                return detailpage;
422            }
423        }
424        return null;
425    }
426
427    /**
428     * Returns the default model page.<p>
429     *
430     * @return the default model page
431     */
432    public CmsModelPageConfig getDefaultModelPage() {
433
434        List<CmsModelPageConfig> modelPages = getModelPages();
435        for (CmsModelPageConfig modelPageConfig : getModelPages()) {
436            if (modelPageConfig.isDefault()) {
437                return modelPageConfig;
438            }
439        }
440        if (modelPages.isEmpty()) {
441            return null;
442        }
443        return modelPages.get(0);
444    }
445
446    /**
447     * Gets the detail information for this sitemap config data object.<p>
448     *
449     * @param cms the CMS context
450     * @return the list of detail information
451     */
452    public List<DetailInfo> getDetailInfos(CmsObject cms) {
453
454        List<DetailInfo> result = Lists.newArrayList();
455        List<CmsDetailPageInfo> detailPages = getAllDetailPages(true);
456        Collections.reverse(detailPages); // make sure primary detail pages come later in the list and override other detail pages for the same type
457        Map<String, CmsDetailPageInfo> primaryDetailPageMapByType = Maps.newHashMap();
458        for (CmsDetailPageInfo pageInfo : detailPages) {
459            primaryDetailPageMapByType.put(pageInfo.getType(), pageInfo);
460        }
461        for (CmsResourceTypeConfig typeConfig : getResourceTypes()) {
462            String typeName = typeConfig.getTypeName();
463            if (((typeConfig.getFolderOrName() == null) || !typeConfig.getFolderOrName().isPageRelative())
464                && primaryDetailPageMapByType.containsKey(typeName)) {
465                String folderPath = typeConfig.getFolderPath(cms, null);
466                CmsDetailPageInfo pageInfo = primaryDetailPageMapByType.get(typeName);
467                result.add(new DetailInfo(folderPath, pageInfo, typeName, getBasePath()));
468            }
469        }
470        return result;
471    }
472
473    /**
474     * Gets the detail pages for a specific type.<p>
475     *
476     * @param type the type name
477     *
478     * @return the list of detail pages for that type
479     */
480    public List<CmsDetailPageInfo> getDetailPagesForType(String type) {
481
482        List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>();
483        CmsResourceTypeConfig typeConfig = getResourceType(type);
484        if (type.startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)
485            || ((typeConfig != null) && !typeConfig.isDetailPagesDisabled())) {
486
487            CmsDetailPageInfo defaultPage = null;
488            for (CmsDetailPageInfo detailpage : getAllDetailPages(true)) {
489                if (detailpage.getType().equals(type)) {
490                    result.add(detailpage);
491                } else if ((defaultPage == null)
492                    && CmsADEManager.DEFAULT_DETAILPAGE_TYPE.equals(detailpage.getType())) {
493                    defaultPage = detailpage;
494                }
495            }
496            if (defaultPage != null) {
497                // add default detail page last
498                result.add(defaultPage);
499            }
500        }
501        return result;
502    }
503
504    /**
505     * Returns all available display formatters.<p>
506     *
507     * @param cms the cms context
508     *
509     * @return the available display formatters
510     */
511    public List<I_CmsFormatterBean> getDisplayFormatters(CmsObject cms) {
512
513        List<I_CmsFormatterBean> result = new ArrayList<I_CmsFormatterBean>();
514
515        for (I_CmsFormatterBean formatter : getCachedFormatters().getFormatters().values()) {
516            if (formatter.isDisplayFormatter()) {
517                result.add(formatter);
518            }
519        }
520        return result;
521    }
522
523    /**
524     * Returns the restricted dynamic functions or <code>null</code>.<p>
525     *
526     * @return the dynamic functions
527     */
528    public Collection<CmsUUID> getDynamicFunctions() {
529
530        CmsADEConfigData parentData = parent();
531        Collection<CmsUUID> result = m_data.getDynamicFunctions();
532        if ((result == null) && (parentData != null)) {
533            result = parentData.getDynamicFunctions();
534        }
535        return result;
536    }
537
538    /**
539     * Returns the formatter change sets for this and all parent sitemaps, ordered by increasing folder depth of the sitemap.<p>
540     *
541     * @return the formatter change sets for all ancestor sitemaps
542     */
543    public List<CmsFormatterChangeSet> getFormatterChangeSets() {
544
545        CmsADEConfigData currentConfig = this;
546        List<CmsFormatterChangeSet> result = Lists.newArrayList();
547        while (currentConfig != null) {
548            CmsFormatterChangeSet changes = currentConfig.getOwnFormatterChangeSet();
549            if (changes != null) {
550                result.add(changes);
551            }
552            currentConfig = currentConfig.parent();
553        }
554        Collections.reverse(result);
555        return result;
556    }
557
558    /**
559     * Gets the formatter configuration for a resource.<p>
560     *
561     * @param cms the current CMS context
562     * @param res the resource for which the formatter configuration should be retrieved
563     *
564     * @return the configuration of formatters for the resource
565     */
566    public CmsFormatterConfiguration getFormatters(CmsObject cms, CmsResource res) {
567
568        if (CmsResourceTypeFunctionConfig.isFunction(res)) {
569
570            CmsFormatterConfigurationCacheState formatters = getCachedFormatters();
571            I_CmsFormatterBean function = formatters.getFormatters().get(res.getStructureId());
572            if (function != null) {
573                return CmsFormatterConfiguration.create(cms, Collections.singletonList(function));
574            } else {
575                if ((!res.getStructureId().isNullUUID())
576                    && cms.existsResource(res.getStructureId(), CmsResourceFilter.IGNORE_EXPIRATION)) {
577                    // usually if it's just been created, but not added to the configuration cache yet
578                    CmsFormatterBeanParser parser = new CmsFormatterBeanParser(cms, new HashMap<>());
579                    try {
580                        function = parser.parse(
581                            CmsXmlContentFactory.unmarshal(cms, cms.readFile(res)),
582                            res.getRootPath(),
583                            "" + res.getStructureId());
584                        return CmsFormatterConfiguration.create(cms, Collections.singletonList(function));
585                    } catch (Exception e) {
586                        LOG.warn(e.getLocalizedMessage(), e);
587                        return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
588                    }
589
590                } else {
591                    // if a new function has been dragged on the page, it doesn't exist in the VFS yet, so we need a different
592                    // instance as a replacement
593                    CmsResource defaultFormatter = CmsFunctionRenderer.getDefaultFunctionInstance(cms);
594                    if (defaultFormatter != null) {
595                        I_CmsFormatterBean defaultFormatterBean = formatters.getFormatters().get(
596                            defaultFormatter.getStructureId());
597                        return CmsFormatterConfiguration.create(cms, Collections.singletonList(defaultFormatterBean));
598                    } else {
599                        LOG.warn("Could not read default formatter for functions.");
600                        return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
601                    }
602                }
603            }
604        } else {
605            try {
606                int resTypeId = res.getTypeId();
607                return getFormatters(
608                    cms,
609                    OpenCms.getResourceManager().getResourceType(resTypeId),
610                    getFormattersFromSchema(cms, res));
611            } catch (CmsLoaderException e) {
612                LOG.warn(e.getLocalizedMessage(), e);
613                return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
614            }
615        }
616    }
617
618    /**
619     * Gets a named function reference.<p>
620     *
621     * @param name the name of the function reference
622     *
623     * @return the function reference for the given name
624     */
625    public CmsFunctionReference getFunctionReference(String name) {
626
627        List<CmsFunctionReference> functionReferences = getFunctionReferences();
628        for (CmsFunctionReference functionRef : functionReferences) {
629            if (functionRef.getName().equals(name)) {
630                return functionRef;
631            }
632        }
633        return null;
634    }
635
636    /**
637     * Gets the list of configured function references.<p>
638     *
639     * @return the list of configured function references
640     */
641    public List<CmsFunctionReference> getFunctionReferences() {
642
643        return internalGetFunctionReferences();
644    }
645
646    /**
647     * Gets the map of external (non-schema) formatters which are inactive in this sub-sitemap.<p>
648     *
649     * @return the map inactive external formatters
650     */
651    public Map<CmsUUID, I_CmsFormatterBean> getInactiveFormatters() {
652
653        CmsFormatterConfigurationCacheState cacheState = getCachedFormatters();
654        Map<CmsUUID, I_CmsFormatterBean> result = Maps.newHashMap(cacheState.getFormatters());
655        result.keySet().removeAll(getActiveFormatters().keySet());
656        return result;
657    }
658
659    /**
660     * Gets the main detail page for a specific type.<p>
661     *
662     * @param type the type name
663     *
664     * @return the main detail page for that type
665     */
666    public CmsDetailPageInfo getMainDetailPage(String type) {
667
668        List<CmsDetailPageInfo> detailPages = getDetailPagesForType(type);
669        if ((detailPages == null) || detailPages.isEmpty()) {
670            return null;
671        }
672        return detailPages.get(0);
673    }
674
675    /**
676     * Gets the list of available model pages.<p>
677     *
678     * @return the list of available model pages
679     */
680    public List<CmsModelPageConfig> getModelPages() {
681
682        return getModelPages(false);
683    }
684
685    /**
686     * Gets the list of available model pages.<p>
687     *
688     * @param includeDisable <code>true</code> to include disabled model pages
689     *
690     * @return the list of available model pages
691     */
692    public List<CmsModelPageConfig> getModelPages(boolean includeDisable) {
693
694        CmsADEConfigData parentData = parent();
695        List<CmsModelPageConfig> parentModelPages;
696        if ((parentData != null) && !m_data.isDiscardInheritedModelPages()) {
697            parentModelPages = parentData.getModelPages();
698        } else {
699            parentModelPages = Collections.emptyList();
700        }
701
702        List<CmsModelPageConfig> result = combineConfigurationElements(
703            parentModelPages,
704            m_data.getOwnModelPageConfig(),
705            includeDisable);
706        return result;
707    }
708
709    /**
710     * Gets the formatter changes for this sitemap configuration.<p>
711     *
712     * @return the formatter change set
713     */
714    public CmsFormatterChangeSet getOwnFormatterChangeSet() {
715
716        return m_data.getFormatterChangeSet();
717    }
718
719    /**
720     * Gets the configuration for the available properties.<p>
721     *
722     * @return the configuration for the available properties
723     */
724    public List<CmsPropertyConfig> getPropertyConfiguration() {
725
726        CmsADEConfigData parentData = parent();
727        List<CmsPropertyConfig> parentProperties;
728        if ((parentData != null) && !m_data.isDiscardInheritedProperties()) {
729            parentProperties = parentData.getPropertyConfiguration();
730        } else {
731            parentProperties = Collections.emptyList();
732        }
733        List<CmsPropertyConfig> result = combineConfigurationElements(
734            parentProperties,
735            m_data.getOwnPropertyConfigurations(),
736            false);
737        return result;
738    }
739
740    /**
741     * Gets the property configuration as a map of CmsXmlContentProperty instances.<p>
742     *
743     * @return the map of property configurations
744     */
745    public Map<String, CmsXmlContentProperty> getPropertyConfigurationAsMap() {
746
747        Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>();
748        for (CmsPropertyConfig propConf : getPropertyConfiguration()) {
749            result.put(propConf.getName(), propConf.getPropertyData());
750        }
751        return result;
752    }
753
754    /**
755     * Returns the resource from which this configuration was read.<p>
756     *
757     * @return the resource from which this configuration was read
758     */
759    public CmsResource getResource() {
760
761        return m_data.getResource();
762    }
763
764    /**
765     * Returns the configuration for a specific resource type.<p>
766     *
767     * @param typeName the name of the type
768     *
769     * @return the resource type configuration for that type
770     */
771    public CmsResourceTypeConfig getResourceType(String typeName) {
772
773        for (CmsResourceTypeConfig type : getResourceTypes()) {
774            if (typeName.equals(type.getTypeName())) {
775                return type;
776            }
777        }
778        return null;
779    }
780
781    /**
782     * Gets a list of all available resource type configurations.<p>
783     *
784     * @return the available resource type configurations
785     */
786    public List<CmsResourceTypeConfig> getResourceTypes() {
787
788        List<CmsResourceTypeConfig> result = internalGetResourceTypes(true);
789        for (CmsResourceTypeConfig config : result) {
790            config.initialize(getCms());
791        }
792        return result;
793    }
794
795    /**
796     * Gets the searchable resource type configurations.<p>
797     *
798     * @param cms the current CMS context
799     * @return the searchable resource type configurations
800     */
801    public Collection<CmsResourceTypeConfig> getSearchableTypes(CmsObject cms) {
802
803        return getResourceTypes();
804    }
805
806    /**
807     * 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>
808     *
809     * @return the set of types for which schema formatters are active
810     */
811    public Set<String> getTypesWithActiveSchemaFormatters() {
812
813        Set<String> result = Sets.newHashSet(getTypesWithModifiableFormatters());
814        for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) {
815            changeSet.applyToTypes(result);
816        }
817        return result;
818    }
819
820    /**
821     * Gets the set of names of resource types which have schema-based formatters that can be enabled or disabled.<p>
822     *
823     * @return the set of names of resource types which have schema-based formatters that can be enabled or disabled
824     */
825    public Set<String> getTypesWithModifiableFormatters() {
826
827        Set<String> result = new HashSet<String>();
828        for (I_CmsResourceType type : OpenCms.getResourceManager().getResourceTypes()) {
829            if (type instanceof CmsResourceTypeXmlContent) {
830                CmsXmlContentDefinition contentDef = null;
831                try {
832                    contentDef = CmsXmlContentDefinition.getContentDefinitionForType(getCms(), type.getTypeName());
833                    if ((contentDef != null) && contentDef.getContentHandler().hasModifiableFormatters()) {
834                        result.add(type.getTypeName());
835                    }
836                } catch (Exception e) {
837                    LOG.error(e.getLocalizedMessage(), e);
838                }
839            }
840        }
841        return result;
842
843    }
844
845    /**
846     * Checks if there are any matching formatters for the given set of containers.<p>
847     *
848     * @param cms the current CMS context
849     * @param resType the resource type for which the formatter configuration should be retrieved
850     * @param containers the page containers
851     *
852     * @return if there are any matching formatters
853     */
854    public boolean hasFormatters(CmsObject cms, I_CmsResourceType resType, Collection<CmsContainer> containers) {
855
856        try {
857            if (CmsXmlDynamicFunctionHandler.TYPE_FUNCTION.equals(resType.getTypeName())
858                || CmsResourceTypeFunctionConfig.TYPE_NAME.equals(resType.getTypeName())) {
859                // dynamic function may match any container
860                return true;
861            }
862            CmsXmlContentDefinition def = CmsXmlContentDefinition.getContentDefinitionForType(
863                cms,
864                resType.getTypeName());
865            CmsFormatterConfiguration schemaFormatters = def.getContentHandler().getFormatterConfiguration(cms, null);
866            CmsFormatterConfiguration formatters = getFormatters(cms, resType, schemaFormatters);
867            for (CmsContainer cont : containers) {
868                if (cont.isEditable()
869                    && (formatters.getAllMatchingFormatters(cont.getType(), cont.getWidth()).size() > 0)) {
870                    return true;
871                }
872            }
873        } catch (CmsException e) {
874            LOG.warn(e.getLocalizedMessage(), e);
875
876        }
877        return false;
878    }
879
880    /**
881     * Returns the value of the "create contents locally" flag.<p>
882     *
883     * If this flag is set, contents of types configured in a super-sitemap will be created in the sub-sitemap (if the user
884     * creates them from the sub-sitemap).
885     *
886     * @return the "create contents locally" flag
887     */
888    public boolean isCreateContentsLocally() {
889
890        return m_data.isCreateContentsLocally();
891    }
892
893    /**
894     * Returns the value of the "discard inherited model pages" flag.<p>
895     *
896     * If this flag is set, inherited model pages will be discarded for this sitemap.<p>
897     *
898     * @return the "discard inherited model pages" flag
899     */
900    public boolean isDiscardInheritedModelPages() {
901
902        return m_data.isDiscardInheritedModelPages();
903    }
904
905    /**
906     * Returns the value of the "discard inherited properties" flag.<p>
907     *
908     * If this is flag is set, inherited property definitions will be discarded for this sitemap.<p>
909     *
910     * @return the "discard inherited properties" flag.<p>
911     */
912    public boolean isDiscardInheritedProperties() {
913
914        return m_data.isDiscardInheritedProperties();
915    }
916
917    /**
918     * Returns the value of the "discard inherited types" flag.<p>
919     *
920     * If this flag is set, inherited resource types from a super-sitemap will be discarded for this sitemap.<p>
921     *
922     * @return the "discard inherited types" flag
923     */
924    public boolean isDiscardInheritedTypes() {
925
926        return m_data.isDiscardInheritedTypes();
927    }
928
929    /**
930     * Returns true if this is a module configuration instead of a normal sitemap configuration.<p>
931     *
932     * @return true if this is a module configuration
933     */
934    public boolean isModuleConfiguration() {
935
936        return m_data.isModuleConfig();
937    }
938
939    /**
940     * Returns true if detail pages from this sitemap should be preferred for links to contents in this sitemap.<p>
941     *
942     * @return true if detail pages from this sitemap should be preferred for links to contents in this sitemap
943     */
944    public boolean isPreferDetailPagesForLocalContents() {
945
946        return m_data.isPreferDetailPagesForLocalContents();
947    }
948
949    /**
950     * Fetches the parent configuration of this configuration.<p>
951     *
952     * If this configuration is a sitemap configuration with no direct parent configuration,
953     * the module configuration will be returned. If this configuration already is a module configuration,
954     * null will be returned.<p>
955     *
956     * @return the parent configuration
957     */
958    public CmsADEConfigData parent() {
959
960        Optional<CmsADEConfigurationSequence> parentPath = m_configSequence.getParent();
961        if (parentPath.isPresent()) {
962            CmsADEConfigDataInternal internalData = parentPath.get().getConfig();
963            return new CmsADEConfigData(internalData, m_cache, parentPath.get());
964        } else {
965            return null;
966        }
967    }
968
969    /**
970     * Creates the content directory for this configuration node if possible.<p>
971     *
972     * @throws CmsException if something goes wrong
973     */
974    protected void createContentDirectory() throws CmsException {
975
976        if (!isModuleConfiguration()) {
977            String contentFolder = getContentFolderPath();
978            if (!getCms().existsResource(contentFolder)) {
979                getCms().createResource(
980                    contentFolder,
981                    OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.getStaticTypeName()));
982            }
983        }
984    }
985
986    /**
987     * Gets the CMS object used for VFS operations.<p>
988     *
989     * @return the CMS object used for VFS operations
990     */
991    protected CmsObject getCms() {
992
993        return m_cache.getCms();
994    }
995
996    /**
997     * Gets the CMS object used for VFS operations.<p>
998     *
999     * @return the CMS object
1000     */
1001    protected CmsObject getCmsObject() {
1002
1003        return getCms();
1004    }
1005
1006    /**
1007     * Helper method to converts a list of detail pages to a map from type names to lists of detail pages for each type.<p>
1008     *
1009     * @param detailPages the list of detail pages
1010     *
1011     * @return the map of detail pages
1012     */
1013    protected Map<String, List<CmsDetailPageInfo>> getDetailPagesMap(List<CmsDetailPageInfo> detailPages) {
1014
1015        Map<String, List<CmsDetailPageInfo>> result = Maps.newHashMap();
1016        for (CmsDetailPageInfo detailpage : detailPages) {
1017            String type = detailpage.getType();
1018            if (!result.containsKey(type)) {
1019                result.put(type, new ArrayList<CmsDetailPageInfo>());
1020            }
1021            result.get(type).add(detailpage);
1022        }
1023        return result;
1024    }
1025
1026    /**
1027     * Collects the folder types in a map.<p>
1028     *
1029     * @return the map of folder types
1030     *
1031     * @throws CmsException if something goes wrong
1032     */
1033    protected Map<String, String> getFolderTypes() throws CmsException {
1034
1035        Map<String, String> result = new HashMap<String, String>();
1036        CmsObject cms = OpenCms.initCmsObject(getCms());
1037        if (m_data.isModuleConfig()) {
1038            Set<String> siteRoots = OpenCms.getSiteManager().getSiteRoots();
1039            for (String siteRoot : siteRoots) {
1040                cms.getRequestContext().setSiteRoot(siteRoot);
1041                for (CmsResourceTypeConfig config : getResourceTypes()) {
1042                    if (!config.isDetailPagesDisabled()) {
1043                        String typeName = config.getTypeName();
1044                        if (!config.isPageRelative()) { // elements stored with container pages can not be used as detail contents
1045                            String folderPath = config.getFolderPath(cms, null);
1046                            result.put(CmsStringUtil.joinPaths(folderPath, "/"), typeName);
1047                        }
1048                    }
1049                }
1050            }
1051        } else {
1052            for (CmsResourceTypeConfig config : getResourceTypes()) {
1053                if (!config.isDetailPagesDisabled()) {
1054                    String typeName = config.getTypeName();
1055                    if (!config.isPageRelative()) { // elements stored with container pages can not be used as detail contents
1056                        String folderPath = config.getFolderPath(getCms(), null);
1057                        result.put(CmsStringUtil.joinPaths(folderPath, "/"), typeName);
1058                    }
1059                }
1060            }
1061        }
1062        return result;
1063    }
1064
1065    /**
1066     * Gets the formatter configuration for a resource type.<p>
1067     *
1068     * @param cms the current CMS context
1069     * @param resType the resource type
1070     * @param schemaFormatters the resource schema formatters
1071     *
1072     * @return the configuration of formatters for the resource type
1073     */
1074    protected CmsFormatterConfiguration getFormatters(
1075        CmsObject cms,
1076        I_CmsResourceType resType,
1077        CmsFormatterConfiguration schemaFormatters) {
1078
1079        String typeName = resType.getTypeName();
1080        CmsFormatterConfigurationCacheState formatterCacheState = getCachedFormatters();
1081        List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>();
1082        Set<String> types = new HashSet<String>();
1083        types.add(typeName);
1084        for (CmsFormatterChangeSet changeSet : getFormatterChangeSets()) {
1085            if (changeSet != null) {
1086                changeSet.applyToTypes(types);
1087            }
1088        }
1089        if ((schemaFormatters != null) && types.contains(typeName)) {
1090            for (I_CmsFormatterBean formatter : schemaFormatters.getAllFormatters()) {
1091                formatters.add(formatter);
1092            }
1093
1094        }
1095        Map<CmsUUID, I_CmsFormatterBean> externalFormattersById = Maps.newHashMap();
1096        for (I_CmsFormatterBean formatter : formatterCacheState.getFormattersForType(typeName, true)) {
1097            externalFormattersById.put(new CmsUUID(formatter.getId()), formatter);
1098        }
1099        applyAllFormatterChanges(externalFormattersById, formatterCacheState);
1100        for (I_CmsFormatterBean formatter : externalFormattersById.values()) {
1101            if (formatter.getResourceTypeNames().contains(typeName)) {
1102                formatters.add(formatter);
1103            }
1104        }
1105        return CmsFormatterConfiguration.create(cms, formatters);
1106    }
1107
1108    /**
1109     * Gets the formatters from the schema.<p>
1110     *
1111     * @param cms the current CMS context
1112     * @param res the resource for which the formatters should be retrieved
1113     *
1114     * @return the formatters from the schema
1115     */
1116    protected CmsFormatterConfiguration getFormattersFromSchema(CmsObject cms, CmsResource res) {
1117
1118        try {
1119            return OpenCms.getResourceManager().getResourceType(res.getTypeId()).getFormattersForResource(cms, res);
1120        } catch (CmsException e) {
1121            LOG.error(e.getLocalizedMessage(), e);
1122            return CmsFormatterConfiguration.EMPTY_CONFIGURATION;
1123        }
1124    }
1125
1126    /**
1127     * Internal method for getting the function references.<p>
1128     *
1129     * @return the function references
1130     */
1131    protected List<CmsFunctionReference> internalGetFunctionReferences() {
1132
1133        CmsADEConfigData parentData = parent();
1134        if ((parentData == null)) {
1135            if (m_data.isModuleConfig()) {
1136                return Collections.unmodifiableList(m_data.getFunctionReferences());
1137            } else {
1138                return Lists.newArrayList();
1139            }
1140        } else {
1141            return parentData.internalGetFunctionReferences();
1142
1143        }
1144    }
1145
1146    /**
1147     * Helper method for getting the list of resource types.<p>
1148     *
1149     * @param filterDisabled true if disabled types should be filtered from the result
1150     *
1151     * @return the list of resource types
1152     */
1153    protected List<CmsResourceTypeConfig> internalGetResourceTypes(boolean filterDisabled) {
1154
1155        CmsADEConfigData parentData = parent();
1156        List<CmsResourceTypeConfig> parentResourceTypes = null;
1157        if (parentData == null) {
1158            parentResourceTypes = Lists.newArrayList();
1159        } else {
1160            parentResourceTypes = Lists.newArrayList();
1161            for (CmsResourceTypeConfig typeConfig : parentData.internalGetResourceTypes(false)) {
1162                CmsResourceTypeConfig copiedType = typeConfig.copy(m_data.isDiscardInheritedTypes());
1163                parentResourceTypes.add(copiedType);
1164            }
1165        }
1166        List<CmsResourceTypeConfig> result = combineConfigurationElements(
1167            parentResourceTypes,
1168            m_data.getOwnResourceTypes(),
1169            true);
1170        if (m_data.isCreateContentsLocally()) {
1171            for (CmsResourceTypeConfig typeConfig : result) {
1172                typeConfig.updateBasePath(
1173                    CmsStringUtil.joinPaths(m_data.getBasePath(), CmsADEManager.CONTENT_FOLDER_NAME));
1174            }
1175        }
1176        if (filterDisabled) {
1177            Iterator<CmsResourceTypeConfig> iter = result.iterator();
1178            while (iter.hasNext()) {
1179                CmsResourceTypeConfig typeConfig = iter.next();
1180                if (typeConfig.isDisabled()) {
1181                    iter.remove();
1182                }
1183            }
1184        }
1185        return result;
1186    }
1187
1188    /**
1189     * Merges two lists of detail pages, one from a parent configuration and one from a child configuration.<p>
1190     *
1191     * @param parentDetailPages the parent's detail pages
1192     * @param ownDetailPages the child's detail pages
1193     *
1194     * @return the merged detail pages
1195     */
1196    protected List<CmsDetailPageInfo> mergeDetailPages(
1197        List<CmsDetailPageInfo> parentDetailPages,
1198        List<CmsDetailPageInfo> ownDetailPages) {
1199
1200        List<CmsDetailPageInfo> parentDetailPageCopies = Lists.newArrayList();
1201        for (CmsDetailPageInfo info : parentDetailPages) {
1202            parentDetailPageCopies.add(info.copyAsInherited());
1203        }
1204
1205        List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>();
1206        Map<String, List<CmsDetailPageInfo>> resultDetailPageMap = Maps.newHashMap();
1207        resultDetailPageMap.putAll(getDetailPagesMap(parentDetailPageCopies));
1208        resultDetailPageMap.putAll(getDetailPagesMap(ownDetailPages));
1209        result = new ArrayList<CmsDetailPageInfo>();
1210        for (List<CmsDetailPageInfo> pages : resultDetailPageMap.values()) {
1211            result.addAll(pages);
1212        }
1213        return result;
1214    }
1215
1216    /**
1217     * Helper method to correct paths in detail page beans if the corresponding resources have been moved.<p>
1218     *
1219     * @param detailPages the original list of detail pages
1220     *
1221     * @return the corrected list of detail pages
1222     */
1223    protected List<CmsDetailPageInfo> updateUris(List<CmsDetailPageInfo> detailPages) {
1224
1225        List<CmsDetailPageInfo> result = new ArrayList<CmsDetailPageInfo>();
1226        for (CmsDetailPageInfo page : detailPages) {
1227            CmsUUID structureId = page.getId();
1228            try {
1229                String rootPath = OpenCms.getADEManager().getRootPath(
1230                    structureId,
1231                    getCms().getRequestContext().getCurrentProject().isOnlineProject());
1232                String iconClasses;
1233                if (page.getType().startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)) {
1234                    iconClasses = CmsIconUtil.getIconClasses(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION, null, false);
1235                } else {
1236                    iconClasses = CmsIconUtil.getIconClasses(page.getType(), null, false);
1237                }
1238                CmsDetailPageInfo correctedPage = new CmsDetailPageInfo(
1239                    structureId,
1240                    rootPath,
1241                    page.getType(),
1242                    iconClasses);
1243                result.add(page.isInherited() ? correctedPage.copyAsInherited() : correctedPage);
1244            } catch (CmsException e) {
1245                LOG.warn(e.getLocalizedMessage(), e);
1246            }
1247        }
1248        return result;
1249    }
1250}