001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.configuration;
029
030import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReference;
031import org.opencms.ade.configuration.CmsADEConfigDataInternal.ConfigReferenceMeta;
032import org.opencms.ade.configuration.formatters.CmsFormatterChangeSet;
033import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache;
034import org.opencms.ade.containerpage.shared.CmsCntPageData.ElementDeleteMode;
035import org.opencms.ade.detailpage.CmsDetailPageInfo;
036import org.opencms.ade.galleries.CmsAddContentRestriction;
037import org.opencms.file.CmsFile;
038import org.opencms.file.CmsObject;
039import org.opencms.file.CmsResource;
040import org.opencms.file.CmsResourceFilter;
041import org.opencms.file.types.I_CmsResourceType;
042import org.opencms.gwt.CmsIconUtil;
043import org.opencms.i18n.CmsLocaleManager;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.CmsRuntimeException;
047import org.opencms.main.OpenCms;
048import org.opencms.module.CmsModule;
049import org.opencms.relations.CmsLink;
050import org.opencms.util.CmsFileUtil;
051import org.opencms.util.CmsStringUtil;
052import org.opencms.util.CmsUUID;
053import org.opencms.xml.containerpage.CmsFormatterBean;
054import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler;
055import org.opencms.xml.containerpage.I_CmsFormatterBean;
056import org.opencms.xml.content.CmsXmlContent;
057import org.opencms.xml.content.CmsXmlContentFactory;
058import org.opencms.xml.content.CmsXmlContentProperty;
059import org.opencms.xml.content.CmsXmlContentProperty.Visibility;
060import org.opencms.xml.content.CmsXmlContentRootLocation;
061import org.opencms.xml.content.I_CmsXmlContentLocation;
062import org.opencms.xml.content.I_CmsXmlContentValueLocation;
063import org.opencms.xml.types.CmsXmlVarLinkValue;
064import org.opencms.xml.types.CmsXmlVfsFileValue;
065
066import java.util.ArrayList;
067import java.util.HashSet;
068import java.util.LinkedHashMap;
069import java.util.LinkedHashSet;
070import java.util.List;
071import java.util.Locale;
072import java.util.Map;
073import java.util.Set;
074
075import org.apache.commons.logging.Log;
076
077import com.google.common.collect.Lists;
078
079/**
080 * A class to parse ADE sitemap or module configuration files and create configuration objects from them.<p>
081 */
082public class CmsConfigurationReader {
083
084    /**
085     * Enum describing how to deal with inherited properties.
086     */
087    enum DiscardPropertiesMode {
088
089        /** Remove properties from parent sitemaps. */
090        discard("true"),
091
092        /** Inherit properties from parent sitemaps. */
093        keep("false"),
094
095        /** Remove properties from parent sitemaps, and mark properties defined in this sitemap as 'top', which moves them over the properties which are only defined in opencms-workplace.xml/opencms-modules.xml. */
096        top("top");
097
098        /** The value representing the mode in an actual sitemap configuration content. */
099        private String m_stringValue;
100
101        /**
102         * Creates the enum value.
103         *
104         * @param stringValue the string value from the configuration
105         */
106        private DiscardPropertiesMode(String stringValue) {
107
108            m_stringValue = stringValue;
109
110        }
111
112        /**
113         * Gets the string value occurring in the  sitemap configuration.
114         *
115         * @return the string value
116         */
117        public String getStringValue() {
118
119            return m_stringValue;
120        }
121    }
122
123    /** The default locale for configuration objects. */
124    public static final Locale DEFAULT_LOCALE = CmsLocaleManager.getLocale("en");
125
126    /** The AddContentReplacements node name. */
127    public static final String N_ADD_CONTENT_RESTRICTION = "AddContentRestriction";
128
129    /** Node name for added formatters. */
130    public static final String N_ADD_FORMATTER = "AddFormatter";
131
132    /** Node name for the nested content with the added formatters. */
133    public static final String N_ADD_FORMATTERS = "AddFormatters";
134
135    /** The AddPlugin node name. */
136    public static final String N_ADD_PLUGIN = "AddPlugin";
137
138    /** The AddPlugins node name. */
139    public static final String N_ADD_PLUGINS = "AddPlugins";
140
141    /** The Attribute node name. */
142    public static final String N_ATTRIBUTE = "Attribute";
143
144    /** Node name for the attribute editor configuration reference. */
145    public static final String N_ATTRIBUTE_EDITOR_CONFIG = "AttributeEditorConfig";
146
147    /** Node name for the 'Check reuse' option. */
148    public static final String N_CHECK_REUSE = "CheckReuse";
149
150    public static final String N_CONTENT_FOLDER = "ContentFolder";
151
152    /** The CopyInModels node name. */
153    public static final String N_COPY_IN_MODELS = "CopyInModels";
154
155    /** The create content locally node name. */
156    public static final String N_CREATE_CONTENTS_LOCALLY = "CreateContentsLocally";
157
158    /** The default node name. */
159    public static final String N_DEFAULT = "Default";
160
161    /** The description node name. */
162    public static final String N_DESCRIPTION = "Description";
163
164    /** The detail page node name. */
165    public static final String N_DETAIL_PAGE = "DetailPage";
166
167    /** The detail pages disabled node name. */
168    public static final String N_DETAIL_PAGES_DISABLED = "DetailPagesDisabled";
169
170    /** The disabled node name. */
171    public static final String N_DISABLED = "Disabled";
172
173    /** The DisabledFunctionsMode node name. */
174    public static final String N_DISABLED_FUNCTIONS_MODE = "DisabledFunctionsMode";
175
176    /** The DisabledTypesMode node name. */
177    public static final String N_DISABLED_TYPES_MODE = "DisabledTypesMode";
178
179    /** The discard model pages node name. */
180    public static final String N_DISCARD_MODEL_PAGES = "DiscardModelPages";
181
182    /** The discard properties node name. */
183    public static final String N_DISCARD_PROPERTIES = "DiscardProperties";
184
185    /** The discard types node name. */
186    public static final String N_DISCARD_TYPES = "DiscardTypes";
187
188    /** The display name node name. */
189    public static final String N_DISPLAY_NAME = "DisplayName";
190
191    /** The element view node name. */
192    public static final String N_ELEMENT_VIEW = "ElementView";
193
194    /** The error node name. */
195    public static final String N_ERROR = "Error";
196
197    /** The 'exclude external detail contents' node name. */
198    public static final String N_EXCLUDE_EXTERNAL_DETAIL_CONTENTS = "ExcludeExternalDetailContents";
199
200    /** The folder node name. */
201    public static final String N_FOLDER = "Folder";
202
203    /** The formatter node name. */
204    public static final String N_FORMATTER = "Formatter";
205
206    /** The function node name. */
207    public static final String N_FUNCTION = "Function";
208
209    /** The function node name. */
210    public static final String N_FUNCTION_DEFAULT_PAGE = "FunctionDefaultPage";
211
212    /** The function reference node name. */
213    public static final String N_FUNCTION_REF = "FunctionRef";
214
215    /** The 'include in site selector' node name. */
216    public static final String N_INCLUDE_IN_SITE_SELECTOR = "IncludeInSiteSelector";
217
218    /** The IncludeName node name. */
219    public static final String N_INCLUDE_NAME = "IncludeName";
220
221    /** The is default node name. */
222    public static final String N_IS_DEFAULT = "IsDefault";
223
224    /** The is preview node name. */
225    public static final String N_IS_PREVIEW = "IsPreview";
226
227    /** The JSP node name. */
228    public static final String N_JSP = "Jsp";
229
230    /** The Key node name. */
231    public static final String N_KEY = "Key";
232
233    /** The localization node name. */
234    public static final String N_LOCALIZATION = "Localization";
235
236    /** The master configuration node name. */
237    public static final String N_MASTER_CONFIG = "MasterConfig";
238
239    /** The max width node name. */
240    public static final String N_MAX_WIDTH = "MaxWidth";
241
242    /** The min width node name. */
243    public static final String N_MIN_WIDTH = "MinWidth";
244
245    /** The model page node name. */
246    public static final String N_MODEL_PAGE = "ModelPage";
247
248    /** The folder name node name. */
249    public static final String N_NAME = "Name";
250
251    /** The name pattern node name. */
252    public static final String N_NAME_PATTERN = "NamePattern";
253
254    /** The order node name. */
255    public static final String N_ORDER = "Order";
256
257    /** The page node name. */
258    public static final String N_PAGE = "Page";
259
260    /** The PageRelative node name. */
261    public static final String N_PAGE_RELATIVE = "PageRelative";
262
263    /** The folder path node name. */
264    public static final String N_PATH = "Path";
265
266    /** The Plugin node name. */
267    public static final String N_PLUGIN = "Plugin";
268
269    /** The  PreferDetailPagesForLocalContents node name. */
270    public static final String N_PREFER_DETAIL_PAGES_FOR_LOCAL_CONTENTS = "PreferDetailPagesForLocalContents";
271
272    /** The prefer folder node name. */
273    public static final String N_PREFER_FOLDER = "PreferFolder";
274
275    /** The property node name. */
276    public static final String N_PROPERTY = "Property";
277
278    /** The property name node name. */
279    public static final String N_PROPERTY_NAME = "PropertyName";
280
281    /** XML node name. */
282    public static final String N_PROPERTY_NAME_ALIAS = "PropertyNameAlias";
283
284    /** Node name for the "Remove all formatters"-option. */
285    public static final String N_REMOVE_ALL_FORMATTERS = "RemoveAllFormatters";
286
287    /** Field name for the 'Remove all functions' setting. */
288    public static final String N_REMOVE_ALL_FUNCTIONS = "RemoveAllFunctions";
289
290    /** The RemoveAllPlugins node name. */
291    public static final String N_REMOVE_ALL_PLUGINS = "RemoveAllPlugins";
292
293    /** The RemoveAllSharedSettingOverrides node name. */
294    public static final String N_REMOVE_ALL_SHARED_SETTING_OVERRIDES = "RemoveAllSharedSettingOverrides";
295
296    /** Node name for removed formatters. */
297    public static final String N_REMOVE_FORMATTER = "RemoveFormatter";
298
299    /** Node name for the nested content with the removed formatters. */
300    public static final String N_REMOVE_FORMATTERS = "RemoveFormatters";
301
302    /** The remove function node name. */
303    public static final String N_REMOVE_FUNCTIONS = "RemoveFunctions";
304
305    /** The RemovePlugin node name. */
306    public static final String N_REMOVE_PLUGIN = "RemovePlugin";
307
308    /** The RemovePlugins node name. */
309    public static final String N_REMOVE_PLUGINS = "RemovePlugins";
310
311    /** The resource type node name. */
312    public static final String N_RESOURCE_TYPE = "ResourceType";
313
314    /** The regex rule node name. */
315    public static final String N_RULE_REGEX = "RuleRegex";
316
317    /** The rule type node name. */
318    public static final String N_RULE_TYPE = "RuleType";
319
320    /** The SharedSettingOverride node name. */
321    public static final String N_SHARED_SETTING_OVERRIDE = "SharedSettingOverride";
322
323    /** The ShowInDefaultView node name. */
324    public static final String N_SHOW_IN_DEFAULT_VIEW = "ShowInDefaultView";
325
326    /** The type node name. */
327    public static final String N_TYPE = "Type";
328
329    /** The type name node name. */
330    public static final String N_TYPE_NAME = "TypeName";
331
332    /** The node name for the type ordering mode. */
333    public static final String N_TYPE_ORDERING_MODE = "TypeOrderingMode";
334
335    /** Node name. */
336    public static final String N_USE_FORMATTER_KEYS = "UseFormatterKeys";
337
338    /** The Value node name. */
339    public static final String N_VALUE = "Value";
340
341    /** XML node name. */
342    public static final String N_VALUE_TRANSLATION = "ValueTranslation";
343
344    /** The widget node name. */
345    public static final String N_VISIBILITY = "Visibility";
346
347    /** The widget node name. */
348    public static final String N_WIDGET = "Widget";
349
350    /** The widget configuration node name. */
351    public static final String N_WIDGET_CONFIG = "WidgetConfig";
352
353    /** Scheme for explorer type view links. */
354    public static final String VIEW_SCHEME = "view://";
355
356    /** The log object for this class. */
357    private static final Log LOG = CmsLog.getLog(CmsConfigurationReader.class);
358
359    /** The ElementDeleteMode node name. */
360    private static final String N_ELEMENT_DELETE_MODE = "ElementDeleteMode";
361
362    /** The CMS context used for reading the configuration data. */
363    private CmsObject m_cms;
364
365    /** The parsed detail page configuration elements. */
366    private List<CmsDetailPageInfo> m_detailPageConfigs = new ArrayList<CmsDetailPageInfo>();
367
368    /** The list of configured function references. */
369    private List<CmsFunctionReference> m_functionReferences = new ArrayList<CmsFunctionReference>();
370
371    /** The parsed model page configuration elements. */
372    private List<CmsModelPageConfigWithoutResource> m_modelPageConfigs = new ArrayList<CmsModelPageConfigWithoutResource>();
373
374    /** The parsed property configuration elements. */
375    private List<CmsPropertyConfig> m_propertyConfigs = new ArrayList<CmsPropertyConfig>();
376
377    /** The resource type configuration objects. */
378    private List<CmsResourceTypeConfig> m_resourceTypeConfigs = new ArrayList<CmsResourceTypeConfig>();
379
380    /**
381     * Creates a new configuration reader.<p>
382     *
383     * @param cms the CMS context which should be used to read the configuration data.<p>
384     */
385    public CmsConfigurationReader(CmsObject cms) {
386
387        m_cms = cms;
388    }
389
390    /**
391     * Gets the string value of an XML content location.<p>
392     *
393     * @param cms the CMS context to use
394     * @param location an XML content location
395     *
396     * @return the string value of that XML content location
397     */
398    public static String getString(CmsObject cms, I_CmsXmlContentValueLocation location) {
399
400        if (location == null) {
401            return null;
402        }
403        return location.asString(cms);
404    }
405
406    /**
407     * Helper method to parse a property.<p>
408     *
409     * @param cms the CMS context to use
410     * @param field the location of the parent value
411     *
412     * @return the parsed property configuration
413     */
414    public static CmsPropertyConfig parseProperty(CmsObject cms, I_CmsXmlContentLocation field) {
415
416        String name = getString(cms, field.getSubValue(N_PROPERTY_NAME));
417        String includeName = getString(cms, field.getSubValue(N_INCLUDE_NAME));
418        String widget = getString(cms, field.getSubValue(N_WIDGET));
419        String widgetConfig = getString(cms, field.getSubValue(N_WIDGET_CONFIG));
420        String ruleRegex = getString(cms, field.getSubValue(N_RULE_REGEX));
421        String ruleType = getString(cms, field.getSubValue(N_RULE_TYPE));
422        String default1 = getString(cms, field.getSubValue(N_DEFAULT));
423        String error = getString(cms, field.getSubValue(N_ERROR));
424        String niceName = getString(cms, field.getSubValue(N_DISPLAY_NAME));
425        String description = getString(cms, field.getSubValue(N_DESCRIPTION));
426        String preferFolder = getString(cms, field.getSubValue(N_PREFER_FOLDER));
427        String aliasName = getString(cms, field.getSubValue(N_PROPERTY_NAME_ALIAS));
428        String valueTranslations = getString(cms, field.getSubValue(N_VALUE_TRANSLATION));
429
430        String disabledStr = getString(cms, field.getSubValue(N_DISABLED));
431        boolean disabled = ((disabledStr != null) && Boolean.parseBoolean(disabledStr));
432
433        String orderStr = getString(cms, field.getSubValue(N_ORDER));
434        int order = I_CmsConfigurationObject.DEFAULT_ORDER;
435
436        try {
437            order = Integer.parseInt(orderStr);
438        } catch (NumberFormatException e) {
439            // noop
440        }
441
442        Visibility visibility;
443        String visibilityStr = getString(cms, field.getSubValue(N_VISIBILITY));
444        try {
445            // to stay compatible with former visibility option values
446            if ("both".equals(visibilityStr)) {
447                visibilityStr = Visibility.elementAndParentIndividual.name();
448            } else if ("parent".equals(visibilityStr)) {
449                visibilityStr = Visibility.parentShared.name();
450            }
451            visibility = Visibility.valueOf(visibilityStr);
452        } catch (Exception e) {
453            visibility = null;
454        }
455        CmsXmlContentProperty prop = new CmsXmlContentProperty(
456            name,
457            aliasName,
458            "string",
459            visibility,
460            widget,
461            widgetConfig,
462            ruleRegex,
463            ruleType,
464            default1,
465            niceName,
466            description,
467            error,
468            preferFolder,
469            valueTranslations).withIncludeName(includeName);
470        // since these are real properties, using type vfslist makes no sense, so we always use the "string" type
471        CmsPropertyConfig propConfig = new CmsPropertyConfig(prop, disabled, order);
472        return propConfig;
473
474    }
475
476    /**
477     * Returns the list of function references.<p>
478     *
479     * @return the list of function references
480     */
481    public List<CmsFunctionReference> getFunctionReferences() {
482
483        return new ArrayList<CmsFunctionReference>(m_functionReferences);
484    }
485
486    /**
487     * Returns the modelPageConfigs.<p>
488     *
489     * @return the modelPageConfigs
490     */
491    public List<CmsModelPageConfigWithoutResource> getModelPageConfigs() {
492
493        return m_modelPageConfigs;
494    }
495
496    /**
497     * Parses the formatters to add.<p>
498     *
499     * @param node the parent node
500     * @return the set of keys of the formatters to add
501     */
502    public Set<String> parseAddFormatters(I_CmsXmlContentLocation node) {
503
504        Set<String> addFormatters = new HashSet<String>();
505        for (I_CmsXmlContentValueLocation addLoc : node.getSubValues(N_ADD_FORMATTERS + "/" + N_ADD_FORMATTER)) {
506            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)addLoc.getValue();
507            CmsLink link = value.getLink(m_cms);
508            if (link != null) {
509                CmsUUID structureId = link.getStructureId();
510                if (structureId != null) {
511                    addFormatters.add(structureId.toString());
512                }
513            }
514        }
515        return addFormatters;
516    }
517
518    /**
519     * Parses a configuration XML content and creates a configuration object from it.<p>
520     *
521     * @param basePath the base path
522     * @param content the XML content
523     *
524     * @return the created configuration object with the data from the XML content
525     * @throws CmsException if something goes wrong
526     */
527    public CmsADEConfigDataInternal parseConfiguration(String basePath, CmsXmlContent content) throws CmsException {
528
529        m_detailPageConfigs = Lists.newArrayList();
530        m_functionReferences = Lists.newArrayList();
531        m_modelPageConfigs = Lists.newArrayList();
532        m_propertyConfigs = Lists.newArrayList();
533        m_resourceTypeConfigs = Lists.newArrayList();
534        CmsUUID baseId = null;
535        if (basePath != null) {
536            try {
537                CmsResource folderRes = m_cms.readResource(basePath, CmsResourceFilter.IGNORE_EXPIRATION);
538                baseId = folderRes.getStructureId();
539            } catch (Exception e) {
540                LOG.error(e.getLocalizedMessage(), e);
541            }
542        }
543        if (!content.hasLocale(DEFAULT_LOCALE)) {
544            return CmsADEConfigDataInternal.emptyConfiguration(basePath);
545        }
546        CmsXmlContentRootLocation root = new CmsXmlContentRootLocation(content, DEFAULT_LOCALE);
547        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_RESOURCE_TYPE)) {
548            try {
549                parseResourceTypeConfig(basePath, node);
550            } catch (CmsException e) {
551                LOG.warn(e.getLocalizedMessage(), e);
552            }
553        }
554        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_MODEL_PAGE)) {
555            try {
556                parseModelPage(node);
557            } catch (Exception e) {
558                LOG.warn(e.getLocalizedMessage(), e);
559            }
560        }
561        for (I_CmsXmlContentLocation node : root.getSubValues(N_DETAIL_PAGE)) {
562            try {
563                parseDetailPage(node);
564            } catch (Exception e) {
565                LOG.warn(e.getLocalizedMessage(), e);
566            }
567        }
568
569        for (I_CmsXmlContentLocation node : root.getSubValues(N_FUNCTION_REF)) {
570            parseFunctionReference(node);
571        }
572
573        CmsUUID sharedSettingOverride = null;
574        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_SHARED_SETTING_OVERRIDE)) {
575            sharedSettingOverride = ((CmsXmlVfsFileValue)node.getValue()).getLink(m_cms).getStructureId();
576        }
577
578        boolean removeSharedSettingOverrides = getBoolean(root, N_REMOVE_ALL_SHARED_SETTING_OVERRIDES);
579
580        boolean removeFunctions = false;
581        removeFunctions = getBoolean(root, N_REMOVE_ALL_FUNCTIONS);
582
583        Set<CmsUUID> functions = new LinkedHashSet<>();
584        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_FUNCTION)) {
585            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)node.getValue();
586            CmsLink link = value.getLink(m_cms);
587            if (link != null) {
588                CmsUUID structureId = link.getStructureId();
589                if (structureId != null) {
590                    functions.add(link.getStructureId());
591                }
592            }
593        }
594
595        Set<CmsUUID> functionsToRemove = new LinkedHashSet<>();
596        for (I_CmsXmlContentValueLocation parent : root.getSubValues(N_REMOVE_FUNCTIONS)) {
597            for (I_CmsXmlContentValueLocation node : parent.getSubValues(N_FUNCTION)) {
598                CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)node.getValue();
599                CmsLink link = value.getLink(m_cms);
600                if (link != null) {
601                    CmsUUID structureId = link.getStructureId();
602                    if (structureId != null) {
603                        functionsToRemove.add(link.getStructureId());
604                    }
605                }
606            }
607        }
608
609        boolean removeAllPlugins = getBoolean(root, N_REMOVE_ALL_PLUGINS);
610        Set<CmsUUID> pluginsToRemove = readInternalLinkListTargetIds(root, N_REMOVE_PLUGINS, N_PLUGIN);
611        Set<CmsUUID> pluginsToAdd = readInternalLinkListTargetIds(root, N_ADD_PLUGINS, N_PLUGIN);
612
613        boolean removeAllFormatters = getBoolean(root, N_REMOVE_ALL_FORMATTERS);
614        CmsFormatterChangeSet formatterChangeSet = parseFormatterChangeSet(
615            basePath,
616            root,
617            removeAllFormatters,
618            removeFunctions,
619            functions,
620            functionsToRemove);
621        boolean discardInheritedTypes = getBoolean(root, N_DISCARD_TYPES);
622        // boolean discardInheritedProperties = getBoolean(root, N_DISCARD_PROPERTIES);
623        I_CmsXmlContentValueLocation discardPropertiesLoc = root.getSubValue(N_DISCARD_PROPERTIES);
624        DiscardPropertiesMode discardPropertiesMode = DiscardPropertiesMode.keep;
625        if (discardPropertiesLoc != null) {
626            String discardPropertiesStr = discardPropertiesLoc.getValue().getStringValue(m_cms);
627            for (DiscardPropertiesMode discardMode : DiscardPropertiesMode.values()) {
628                if (discardMode.getStringValue().contentEquals(discardPropertiesStr)) {
629                    discardPropertiesMode = discardMode;
630                }
631            }
632        }
633        for (I_CmsXmlContentLocation node : root.getSubValues(N_PROPERTY)) {
634            parseProperty(node, discardPropertiesMode);
635        }
636
637        boolean discardInheritedModelPages = getBoolean(root, N_DISCARD_MODEL_PAGES);
638
639        boolean createContentsLocally = getBoolean(root, N_CREATE_CONTENTS_LOCALLY);
640        CmsContentFolderOption contentFolderOption = null;
641        for (I_CmsXmlContentValueLocation contentFolderLoc : root.getSubValues(N_CONTENT_FOLDER)) {
642            contentFolderOption = parseContentFolderOption(contentFolderLoc);
643        }
644        boolean preferDetailPagesForLocalContents = getBoolean(root, N_PREFER_DETAIL_PAGES_FOR_LOCAL_CONTENTS);
645        boolean exludeExternalDetailContents = getBoolean(root, N_EXCLUDE_EXTERNAL_DETAIL_CONTENTS);
646        boolean includeInSiteSelector = getBoolean(root, N_INCLUDE_IN_SITE_SELECTOR);
647
648        String galleryDisabledTypesStr = getString(root.getSubValue(N_DISABLED_TYPES_MODE));
649        CmsGalleryDisabledTypesMode galleryDisabledTypesMode = null;
650        if (galleryDisabledTypesStr != null) {
651            galleryDisabledTypesMode = CmsGalleryDisabledTypesMode.valueOf(galleryDisabledTypesStr);
652        }
653
654        String galleryDisabledFunctionsStr = getString(root.getSubValue(N_DISABLED_FUNCTIONS_MODE));
655        CmsGalleryDisabledTypesMode galleryDisabledFunctionsMode = null;
656        if (galleryDisabledFunctionsStr != null) {
657            galleryDisabledFunctionsMode = CmsGalleryDisabledTypesMode.valueOf(galleryDisabledFunctionsStr);
658        }
659
660        I_CmsXmlContentValueLocation typeOrderingLoc = root.getSubValue(N_TYPE_ORDERING_MODE);
661        CmsTypeOrderingMode typeOrderingMode = null;
662        if (typeOrderingLoc != null) {
663            boolean byDisplayOrder = Boolean.parseBoolean(typeOrderingLoc.getValue().getStringValue(m_cms));
664            typeOrderingMode = byDisplayOrder ? CmsTypeOrderingMode.byDisplayOrder : CmsTypeOrderingMode.latestOnTop;
665        }
666
667        I_CmsXmlContentValueLocation useFormatterKeysLoc = root.getSubValue(N_USE_FORMATTER_KEYS);
668        Boolean useFormatterKeys = null;
669        if (useFormatterKeysLoc != null) {
670            useFormatterKeys = Boolean.valueOf(useFormatterKeysLoc.getValue().getStringValue(m_cms));
671        }
672
673        boolean isModuleConfig = OpenCms.getResourceManager().getResourceType(
674            content.getFile().getTypeId()).getTypeName().equals(CmsADEManager.MODULE_CONFIG_TYPE);
675
676        List<ConfigReference> masterConfigIds = new ArrayList<>();
677        for (I_CmsXmlContentValueLocation masterConfigLoc : root.getSubValues(N_MASTER_CONFIG)) {
678            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)masterConfigLoc.getValue();
679            CmsLink link = value.getUncheckedLink();
680            ConfigReferenceMeta meta = null;
681            if (link != null) {
682                meta = new ConfigReferenceMeta(link.getParameterMap());
683            }
684            CmsUUID id = masterConfigLoc.asId(m_cms);
685            if (id != null) {
686                masterConfigIds.add(new ConfigReference(id, meta));
687            }
688        }
689
690        Map<String, String> attributes = new LinkedHashMap<>();
691        for (I_CmsXmlContentValueLocation mappingLoc : root.getSubValues(N_ATTRIBUTE)) {
692            String key = getString(mappingLoc.getSubValue(N_KEY)).trim();
693            String value = getString(mappingLoc.getSubValue(N_VALUE)).trim();
694            attributes.put(key, value);
695        }
696
697        I_CmsXmlContentValueLocation attributeEditorConfigLoc = root.getSubValue(N_ATTRIBUTE_EDITOR_CONFIG);
698        CmsUUID attributeEditorConfigId = null;
699        if (attributeEditorConfigLoc != null) {
700            attributeEditorConfigId = attributeEditorConfigLoc.asId(m_cms);
701        }
702
703        CmsAddContentRestriction addContentRestriction = CmsAddContentRestriction.read(
704            m_cms,
705            root,
706            N_ADD_CONTENT_RESTRICTION);
707
708        CmsADEConfigDataInternal result = new CmsADEConfigDataInternal(
709            m_cms,
710            content.getFile(),
711            isModuleConfig,
712            basePath,
713            baseId,
714            masterConfigIds,
715            m_resourceTypeConfigs,
716            galleryDisabledTypesMode,
717            galleryDisabledFunctionsMode,
718            discardInheritedTypes,
719            m_propertyConfigs,
720            discardPropertiesMode,
721            m_detailPageConfigs,
722            m_modelPageConfigs,
723            m_functionReferences,
724            discardInheritedModelPages,
725            createContentsLocally,
726            contentFolderOption,
727            preferDetailPagesForLocalContents,
728            exludeExternalDetailContents,
729            includeInSiteSelector,
730            formatterChangeSet,
731            removeFunctions,
732            functions,
733            functionsToRemove,
734            removeAllPlugins,
735            pluginsToAdd,
736            pluginsToRemove,
737            useFormatterKeys,
738            typeOrderingMode,
739            addContentRestriction,
740            sharedSettingOverride,
741            removeSharedSettingOverrides,
742            attributeEditorConfigId,
743            attributes);
744        return result;
745    }
746
747    /**
748     * Parses the content folder configuration.
749     *
750     * @param location the XML content location from which to read the data.
751     *
752     * @return the content folder configuration
753     */
754    public CmsContentFolderOption parseContentFolderOption(I_CmsXmlContentLocation location) {
755
756        if (location == null) {
757            return null;
758        }
759        I_CmsXmlContentValueLocation defaultLoc = location.getSubValue(N_DEFAULT);
760        I_CmsXmlContentValueLocation pathLoc = location.getSubValue(N_PATH);
761        if (defaultLoc != null) {
762            return new CmsContentFolderOption(null);
763        }
764        if (pathLoc != null) {
765            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)pathLoc.getValue();
766            if ((value.getUncheckedLink() != null) && (value.getUncheckedLink().getStructureId() != null)) {
767
768                return new CmsContentFolderOption(value.getUncheckedLink().getStructureId());
769            }
770        }
771        if (LOG.isWarnEnabled()) {
772            LOG.warn(location.getDocument().getFile().getRootPath() + ": invalid content folder");
773        }
774        return null;
775    }
776
777    /**
778     * Parses a folder which may either be given as a path or as a folder name.<p>
779     *
780     * @param basePath the  base path for the configuration
781     * @param location the XML content node from which to parse the folder
782     * @return the folder bean
783     *
784     * @throws CmsException if something goes wrong
785     */
786    public CmsContentFolderDescriptor parseFolderOrName(String basePath, I_CmsXmlContentLocation location)
787    throws CmsException {
788
789        if (location == null) {
790            return null;
791        }
792        I_CmsXmlContentValueLocation nameLoc = location.getSubValue(N_NAME);
793        I_CmsXmlContentValueLocation pathLoc = location.getSubValue(N_PATH);
794        I_CmsXmlContentValueLocation pageRelativeLoc = location.getSubValue(N_PAGE_RELATIVE);
795        if (nameLoc != null) {
796            String name = nameLoc.asString(m_cms);
797            return new CmsContentFolderDescriptor(
798                basePath == null ? null : CmsStringUtil.joinPaths(basePath, CmsADEManager.CONTENT_FOLDER_NAME),
799                name);
800        } else if (pathLoc != null) {
801            String path = pathLoc.asString(m_cms);
802            CmsResource folder = m_cms.readResource(path);
803            return new CmsContentFolderDescriptor(folder);
804        } else if (pageRelativeLoc != null) {
805            return CmsContentFolderDescriptor.createPageRelativeFolderDescriptor();
806        } else {
807            return null;
808        }
809    }
810
811    /**
812     * Parses a formatter bean.<p>
813     *
814     * @param typeName the type name for which the formatter is being parsed
815     * @param node the node from which to parse the formatter data
816     *
817     * @return the formatter bean from the XML
818     */
819    public CmsFormatterBean parseFormatter(String typeName, I_CmsXmlContentLocation node) {
820
821        String type = getString(node.getSubValue(N_TYPE));
822        String minWidth = getString(node.getSubValue(N_MIN_WIDTH));
823        String maxWidth = getString(node.getSubValue(N_MAX_WIDTH));
824        boolean preview = false;
825        I_CmsXmlContentValueLocation previewLoc = node.getSubValue(N_IS_PREVIEW);
826        preview = (previewLoc != null) && Boolean.parseBoolean(previewLoc.asString(m_cms));
827        String jsp = m_cms.getRequestContext().addSiteRoot(getString(node.getSubValue(N_JSP)));
828        boolean searchContent = true;
829        CmsFormatterBean formatterBean = new CmsFormatterBean(
830            type,
831            jsp,
832            minWidth,
833            maxWidth,
834            "" + preview,
835            "" + searchContent,
836            null);
837        return formatterBean;
838
839    }
840
841    /**
842     * Parses model page data from the XML content.<p>
843     *
844     * @param node the XML content node
845     */
846    public void parseModelPage(I_CmsXmlContentLocation node) {
847
848        CmsXmlVfsFileValue pageValue = (CmsXmlVfsFileValue)node.getSubValue(N_PAGE).getValue();
849        CmsLink link = pageValue.getUncheckedLink();
850        if ((link == null) || (link.getStructureId() == null)) {
851            return;
852        }
853        I_CmsXmlContentValueLocation disabledLoc = node.getSubValue(N_DISABLED);
854        boolean disabled = (disabledLoc != null) && Boolean.parseBoolean(disabledLoc.asString(m_cms));
855        I_CmsXmlContentValueLocation defaultLoc = node.getSubValue(N_IS_DEFAULT);
856        boolean isDefault = (defaultLoc != null) && Boolean.parseBoolean(defaultLoc.asString(m_cms));
857        CmsModelPageConfigWithoutResource modelPage = new CmsModelPageConfigWithoutResource(
858            link.getStructureId(),
859            isDefault,
860            disabled);
861        m_modelPageConfigs.add(modelPage);
862
863    }
864
865    /**
866     * Parses the set of formatters to remove.<p>
867     *
868     * @param node the parent node
869     * @return the set of formatters to remove
870     */
871    public Set<String> parseRemoveFormatters(I_CmsXmlContentLocation node) {
872
873        Set<String> removeFormatters = new HashSet<String>();
874        for (I_CmsXmlContentValueLocation removeLoc : node.getSubValues(
875            N_REMOVE_FORMATTERS + "/" + N_REMOVE_FORMATTER)) {
876            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)removeLoc.getValue();
877            CmsLink link = value.getLink(m_cms);
878            if (link != null) {
879                CmsUUID structureId = link.getStructureId();
880                if (structureId != null) {
881                    removeFormatters.add(structureId.toString());
882                }
883            }
884        }
885        return removeFormatters;
886    }
887
888    /**
889     * Parses a resource type configuration element from the XML content.<p>
890     *
891     * @param basePath the base path of the configuration
892     * @param node the XML configuration node
893     * @throws CmsException if something goes wrong
894     */
895    public void parseResourceTypeConfig(String basePath, I_CmsXmlContentLocation node) throws CmsException {
896
897        I_CmsXmlContentValueLocation typeNameLoc = node.getSubValue(N_TYPE_NAME);
898        String typeName = typeNameLoc.asString(m_cms);
899        CmsContentFolderDescriptor folderOrName = parseFolderOrName(basePath, node.getSubValue(N_FOLDER));
900        I_CmsXmlContentValueLocation disabledLoc = node.getSubValue(N_DISABLED);
901        boolean disabled = false;
902        boolean addDisabled = false;
903        boolean createDisabled = false;
904        boolean editDisabled = false;
905        boolean listsOnly = false;
906        String disabledStr = disabledLoc == null ? null : disabledLoc.asString(m_cms);
907        boolean availabilityNotSet = false;
908        if (disabledStr != null) {
909            if ("add".equalsIgnoreCase(disabledStr.trim())) {
910                addDisabled = true;
911            } else if ("create".equalsIgnoreCase(disabledStr.trim())) {
912                createDisabled = true;
913            } else if ("createOrEdit".equalsIgnoreCase(disabledStr.trim())) {
914                createDisabled = true;
915                editDisabled = true;
916            } else if ("listsOnly".equalsIgnoreCase(disabledStr.trim())) {
917                listsOnly = true;
918                addDisabled = true;
919            } else {
920                disabled = Boolean.parseBoolean(disabledStr);
921            }
922        } else {
923            availabilityNotSet = true;
924        }
925
926        I_CmsXmlContentValueLocation namePatternLoc = node.getSubValue(N_NAME_PATTERN);
927        String namePattern = null;
928        if (namePatternLoc != null) {
929            namePattern = namePatternLoc.asString(m_cms);
930        }
931
932        boolean detailPagesDisabled = false;
933        I_CmsXmlContentValueLocation detailDisabledLoc = node.getSubValue(N_DETAIL_PAGES_DISABLED);
934        if (detailDisabledLoc != null) {
935            String detailPagesDisabledStr = detailDisabledLoc.asString(m_cms);
936            detailPagesDisabled = Boolean.parseBoolean(detailPagesDisabledStr);
937        }
938
939        Integer order = null;
940        I_CmsXmlContentValueLocation orderLoc = node.getSubValue(N_ORDER);
941        if (orderLoc != null) {
942            try {
943                String orderStr = orderLoc.asString(m_cms);
944                order = Integer.valueOf(orderStr);
945            } catch (NumberFormatException e) {
946                // noop
947            }
948        }
949
950        I_CmsXmlContentValueLocation elementViewLoc = node.getSubValue(N_ELEMENT_VIEW);
951        CmsUUID elementView = null;
952        if (elementViewLoc != null) {
953            try {
954                CmsXmlVarLinkValue elementViewValue = (CmsXmlVarLinkValue)elementViewLoc.getValue();
955                String stringValue = elementViewValue.getStringValue(m_cms);
956                if ("".equals(stringValue)) {
957                    elementView = CmsUUID.getNullUUID();
958                } else if (stringValue.startsWith(VIEW_SCHEME)) {
959                    elementView = new CmsUUID(stringValue.substring(VIEW_SCHEME.length()));
960                } else {
961                    elementView = elementViewValue.getLink(m_cms).getStructureId();
962                }
963            } catch (Exception e) {
964                // in case parsing the link fails, the default element view will be used
965            }
966        }
967
968        I_CmsXmlContentValueLocation locationLoc = node.getSubValue(N_LOCALIZATION);
969        String localization = null;
970        if (locationLoc != null) {
971            CmsXmlVfsFileValue locationValue = (CmsXmlVfsFileValue)locationLoc.getValue();
972            CmsLink link = locationValue.getLink(m_cms);
973            if (null != link) {
974                String stringValue = link.getSitePath(m_cms);
975                // extract bundle base name from the path to the bundle file
976                int lastSlashIndex = stringValue.lastIndexOf("/");
977                String fileName = stringValue.substring(lastSlashIndex + 1);
978                if (CmsFileUtil.getExtension(fileName).equals(".properties")) {
979                    fileName = fileName.substring(0, fileName.length() - ".properties".length());
980                }
981                String localeSuffix = CmsStringUtil.getLocaleSuffixForName(fileName);
982                if ((localeSuffix != null) && fileName.endsWith(localeSuffix)) {
983                    fileName = fileName.substring(0, fileName.length() - localeSuffix.length() - 1);
984                }
985                localization = fileName;
986            }
987        }
988
989        I_CmsXmlContentValueLocation showDefaultViewLoc = node.getSubValue(N_SHOW_IN_DEFAULT_VIEW);
990        Boolean showInDefaultView = null;
991        if (showDefaultViewLoc != null) {
992            showInDefaultView = Boolean.valueOf(
993                Boolean.parseBoolean(showDefaultViewLoc.getValue().getStringValue(m_cms)));
994        }
995
996        I_CmsXmlContentValueLocation copyInModelsLoc = node.getSubValue(N_COPY_IN_MODELS);
997        Boolean copyInModels = null;
998        if (copyInModelsLoc != null) {
999            copyInModels = Boolean.valueOf(Boolean.parseBoolean(copyInModelsLoc.getValue().getStringValue(m_cms)));
1000        }
1001
1002        I_CmsXmlContentValueLocation elementDeleteModeLoc = node.getSubValue(N_ELEMENT_DELETE_MODE);
1003        ElementDeleteMode elementDeleteMode = null;
1004        if (elementDeleteModeLoc != null) {
1005            try {
1006                elementDeleteMode = ElementDeleteMode.valueOf(elementDeleteModeLoc.getValue().getStringValue(m_cms));
1007            } catch (Exception e) {
1008                LOG.warn(e.getLocalizedMessage(), e);
1009            }
1010        }
1011
1012        I_CmsXmlContentValueLocation checkReuseLoc = node.getSubValue(N_CHECK_REUSE);
1013        Boolean checkReuse = null;
1014        if (checkReuseLoc != null) {
1015            try {
1016                checkReuse = Boolean.valueOf(checkReuseLoc.getValue().getStringValue(m_cms));
1017            } catch (Exception e) {
1018                LOG.warn(e.getLocalizedMessage(), e);
1019            }
1020        }
1021
1022        List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>();
1023        for (I_CmsXmlContentValueLocation formatterLoc : node.getSubValues(N_FORMATTER)) {
1024            CmsFormatterBean formatter = parseFormatter(typeName, formatterLoc);
1025            formatters.add(formatter);
1026        }
1027
1028        CmsResourceTypeConfig typeConfig = new CmsResourceTypeConfig(
1029            typeName,
1030            disabled,
1031            folderOrName,
1032            namePattern,
1033            detailPagesDisabled,
1034            addDisabled,
1035            createDisabled,
1036            editDisabled,
1037            listsOnly,
1038            availabilityNotSet,
1039            elementView,
1040            localization,
1041            showInDefaultView,
1042            copyInModels,
1043            order,
1044            elementDeleteMode,
1045            checkReuse);
1046        m_resourceTypeConfigs.add(typeConfig);
1047    }
1048
1049    /**
1050     * Parses the sitemap configuration given the configuration file and base path.<p>
1051     *
1052     * @param basePath the base path
1053     * @param configRes the configuration file resource
1054     * @return the parsed configuration data
1055     * @throws CmsException if something goes wrong
1056     */
1057    public CmsADEConfigDataInternal parseSitemapConfiguration(String basePath, CmsResource configRes)
1058    throws CmsException {
1059
1060        LOG.info("Parsing configuration " + configRes.getRootPath());
1061        CmsFile configFile = m_cms.readFile(configRes);
1062        CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, configFile);
1063        return parseConfiguration(basePath, content);
1064    }
1065
1066    /**
1067     * Reads the configurations of all modules and combines them into a single configuration object.<p>
1068     *
1069     * @return the combined configuration object
1070     */
1071    public List<CmsADEConfigDataInternal> readModuleConfigurations() {
1072
1073        List<CmsADEConfigDataInternal> configurations = new ArrayList<CmsADEConfigDataInternal>();
1074        List<CmsModule> modules = OpenCms.getModuleManager().getAllInstalledModules();
1075        long beginTime = System.currentTimeMillis();
1076        for (CmsModule module : modules) {
1077            String configPath = module.getConfigurationPath();
1078            if (m_cms.existsResource(configPath)) {
1079                try {
1080                    CmsResource configFile = m_cms.readResource(configPath);
1081                    LOG.info("Found module configuration " + configPath + " for module " + module.getName());
1082                    CmsADEConfigDataInternal config = parseSitemapConfiguration(null, configFile);
1083                    configurations.add(config);
1084                } catch (CmsException e) {
1085                    // errors while parsing configuration
1086                    LOG.error(e.getLocalizedMessage(), e);
1087                } catch (CmsRuntimeException e) {
1088                    // may happen during import of org.opencms.ade.configuration module
1089                    LOG.warn(e.getLocalizedMessage(), e);
1090                } catch (Throwable e) {
1091                    LOG.error(e.getLocalizedMessage(), e);
1092                }
1093            }
1094        }
1095        long endTime = System.currentTimeMillis();
1096        LOG.debug("readModuleConfiguations took " + (endTime - beginTime) + "ms");
1097        return configurations;
1098    }
1099
1100    /**
1101     * Helper method to read a boolean value from the XML.<p>
1102     *
1103     * If the element is not found in the XML, false is returned.<p>
1104     *
1105     * @param parent the parent node
1106     * @param name the name of the XML content value
1107     * @return the boolean value
1108     */
1109    protected boolean getBoolean(I_CmsXmlContentLocation parent, String name) {
1110
1111        I_CmsXmlContentValueLocation location = parent.getSubValue(name);
1112        if (location == null) {
1113            return false;
1114        }
1115        String value = location.getValue().getStringValue(m_cms);
1116        return Boolean.parseBoolean(value);
1117    }
1118
1119    /**
1120     * Gets the string value of an XML content location.<p>
1121     *
1122     * @param location an XML content location
1123     *
1124     * @return the string value of that XML content location
1125     */
1126    protected String getString(I_CmsXmlContentValueLocation location) {
1127
1128        return getString(m_cms, location);
1129    }
1130
1131    /**
1132     * Parses the detail pages from an XML content node.<p>
1133     *
1134     * @param node the XML content node
1135     */
1136    protected void parseDetailPage(I_CmsXmlContentLocation node) {
1137
1138        I_CmsXmlContentValueLocation pageLoc = node.getSubValue(N_PAGE);
1139        String typeName = getString(node.getSubValue(N_TYPE));
1140        int qualifierPos = typeName.indexOf(CmsDetailPageInfo.QUALIFIER_SEPARATOR);
1141        String qualifier = null;
1142        if (qualifierPos != -1) {
1143            qualifier = typeName.substring(qualifierPos + 1);
1144            typeName = typeName.substring(0, qualifierPos);
1145        }
1146        CmsXmlVfsFileValue detailPageValue = (CmsXmlVfsFileValue)pageLoc.getValue();
1147        CmsLink uncheckedLink = detailPageValue.getUncheckedLink();
1148        if (uncheckedLink == null) {
1149            LOG.warn(
1150                "Missing detail page link in " + CmsLog.eval(LOG, () -> node.getDocument().getFile().getRootPath()));
1151            return;
1152        }
1153        String page = uncheckedLink.getTarget();
1154        CmsUUID structureId = uncheckedLink.getStructureId();
1155        if (structureId == null) {
1156            return;
1157        }
1158
1159        String iconClasses;
1160        if (typeName.startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)) {
1161            iconClasses = CmsIconUtil.getIconClasses(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION, null, false);
1162        } else {
1163            iconClasses = CmsIconUtil.getIconClasses(typeName, null, false);
1164        }
1165        List<CmsUUID> folders = new ArrayList<>();
1166        for (I_CmsXmlContentValueLocation folderLoc : node.getSubValues(N_FOLDER)) {
1167            CmsXmlVfsFileValue folderValue = (CmsXmlVfsFileValue)folderLoc.getValue();
1168            CmsLink folderLink = folderValue.getUncheckedLink();
1169            if ((folderLink != null) && (folderLink.getStructureId() != null)) {
1170                folders.add(folderLink.getStructureId());
1171            }
1172        }
1173
1174        CmsDetailPageInfo detailPage = new CmsDetailPageInfo(
1175            structureId,
1176            page,
1177            typeName,
1178            qualifier,
1179            folders,
1180            iconClasses);
1181        m_detailPageConfigs.add(detailPage);
1182
1183    }
1184
1185    /**
1186     * Parses the formatter change set.<p>
1187     *
1188     * @param basePath the configuration base path
1189     * @param node the parent node
1190     * @param removeAllFormatters flag, indicating if all formatters that are not explicitly added should be removed
1191     * @param removeFunctions if true, remove functions
1192     * @param functions the functions to add
1193     * @param functionsToRemove the functions to remove
1194     *
1195     * @return the formatter change set
1196     */
1197    protected CmsFormatterChangeSet parseFormatterChangeSet(
1198        String basePath,
1199        I_CmsXmlContentLocation node,
1200        boolean removeAllFormatters,
1201        boolean removeFunctions,
1202        Set<CmsUUID> functions,
1203        Set<CmsUUID> functionsToRemove) {
1204
1205        Set<String> addFormatters = parseAddFormatters(node);
1206        addFormatters.addAll(readLocalFormatters(node));
1207        Set<String> removeFormatters = removeAllFormatters ? new HashSet<String>() : parseRemoveFormatters(node);
1208        String siteRoot = null;
1209        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(basePath)) {
1210            siteRoot = OpenCms.getSiteManager().getSiteRoot(basePath);
1211        }
1212        CmsFormatterChangeSet result = new CmsFormatterChangeSet(
1213            removeFormatters,
1214            addFormatters,
1215            siteRoot,
1216            removeAllFormatters,
1217            removeFunctions,
1218            functions,
1219            functionsToRemove);
1220        return result;
1221    }
1222
1223    /**
1224     * Parses a function reference node.<p>
1225     *
1226     * @param node the function reference node
1227     */
1228    protected void parseFunctionReference(I_CmsXmlContentLocation node) {
1229
1230        String name = node.getSubValue(N_NAME).asString(m_cms);
1231        CmsUUID functionId = node.getSubValue(N_FUNCTION).asId(m_cms);
1232        CmsUUID functionDefaultPageId = null;
1233        I_CmsXmlContentValueLocation defaultPageValue = node.getSubValue(N_FUNCTION_DEFAULT_PAGE);
1234        if (defaultPageValue != null) {
1235            functionDefaultPageId = defaultPageValue.asId(m_cms);
1236        }
1237        I_CmsXmlContentValueLocation orderNode = node.getSubValue(N_ORDER);
1238        int order = I_CmsConfigurationObject.DEFAULT_ORDER;
1239        if (orderNode != null) {
1240            String orderStr = orderNode.asString(m_cms);
1241            try {
1242                order = Integer.parseInt(orderStr);
1243            } catch (NumberFormatException e) {
1244                // noop
1245            }
1246        }
1247        m_functionReferences.add(new CmsFunctionReference(name, functionId, functionDefaultPageId, order));
1248    }
1249
1250    /**
1251     * Parses a single field definition from a content value.<p>
1252     *
1253     * @param field the content value to parse the field from
1254     * @param mode the property discard mode
1255     */
1256    private void parseProperty(I_CmsXmlContentLocation field, DiscardPropertiesMode mode) {
1257
1258        CmsPropertyConfig propConfig = parseProperty(m_cms, field);
1259        if (CmsStringUtil.isEmptyOrWhitespaceOnly(propConfig.getName())) {
1260            return;
1261        }
1262        if (mode == DiscardPropertiesMode.top) {
1263            propConfig = propConfig.cloneWithTop(true);
1264        }
1265        m_propertyConfigs.add(propConfig);
1266    }
1267
1268    /**
1269     * Helper method for reading the target ids from a list of internal links two levels nested.
1270     *
1271     * @param root the parent location
1272     * @param childName the node name for the children
1273     * @param grandchildName the node name for the grandchildren
1274     *
1275     * @return the set of target ids collected from the grandchildren
1276     */
1277    private Set<CmsUUID> readInternalLinkListTargetIds(
1278        I_CmsXmlContentLocation root,
1279        String childName,
1280        String grandchildName) {
1281
1282        Set<CmsUUID> result = new LinkedHashSet<>();
1283        for (I_CmsXmlContentValueLocation parent : root.getSubValues(childName)) {
1284            for (I_CmsXmlContentValueLocation node : parent.getSubValues(grandchildName)) {
1285                CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)node.getValue();
1286                CmsLink link = value.getLink(m_cms);
1287                if (link != null) {
1288                    CmsUUID structureId = link.getStructureId();
1289                    if (structureId != null) {
1290                        result.add(link.getStructureId());
1291                    }
1292                }
1293            }
1294        }
1295        return result;
1296    }
1297
1298    /**
1299     * Reads the local macro or flex formatters from the .formatters folder if present.<p>
1300     *
1301     * @param node the xml content node
1302     *
1303     * @return the local formatters
1304     */
1305    private Set<String> readLocalFormatters(I_CmsXmlContentLocation node) {
1306
1307        Set<String> addFormatters = new HashSet<String>();
1308        String path = m_cms.getSitePath(node.getDocument().getFile());
1309        path = CmsStringUtil.joinPaths(CmsResource.getParentFolder(path), ".formatters");
1310        try {
1311            if (m_cms.existsResource(path, CmsResourceFilter.IGNORE_EXPIRATION)) {
1312                I_CmsResourceType macroType = OpenCms.getResourceManager().getResourceType(
1313                    CmsFormatterConfigurationCache.TYPE_MACRO_FORMATTER);
1314                CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireType(macroType);
1315                List<CmsResource> macroFormatters = m_cms.readResources(path, filter);
1316                for (CmsResource formatter : macroFormatters) {
1317                    addFormatters.add(formatter.getStructureId().toString());
1318                }
1319                I_CmsResourceType flexType = OpenCms.getResourceManager().getResourceType(
1320                    CmsFormatterConfigurationCache.TYPE_FLEX_FORMATTER);
1321                CmsResourceFilter filterFlex = CmsResourceFilter.IGNORE_EXPIRATION.addRequireType(flexType);
1322                List<CmsResource> flexFormatters = m_cms.readResources(path, filterFlex);
1323                for (CmsResource formatter : flexFormatters) {
1324                    addFormatters.add(formatter.getStructureId().toString());
1325                }
1326            }
1327        } catch (CmsException e) {
1328            LOG.warn(e.getMessage(), e);
1329        }
1330        return addFormatters;
1331    }
1332
1333}