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