001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.configuration;
029
030import org.opencms.ade.configuration.formatters.CmsFormatterChangeSet;
031import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache;
032import org.opencms.ade.containerpage.shared.CmsCntPageData.ElementDeleteMode;
033import org.opencms.ade.detailpage.CmsDetailPageInfo;
034import org.opencms.file.CmsFile;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsResourceFilter;
038import org.opencms.file.CmsVfsResourceNotFoundException;
039import org.opencms.file.types.I_CmsResourceType;
040import org.opencms.gwt.CmsIconUtil;
041import org.opencms.i18n.CmsLocaleManager;
042import org.opencms.main.CmsException;
043import org.opencms.main.CmsLog;
044import org.opencms.main.CmsRuntimeException;
045import org.opencms.main.OpenCms;
046import org.opencms.module.CmsModule;
047import org.opencms.relations.CmsLink;
048import org.opencms.util.CmsFileUtil;
049import org.opencms.util.CmsStringUtil;
050import org.opencms.util.CmsUUID;
051import org.opencms.xml.containerpage.CmsFormatterBean;
052import org.opencms.xml.containerpage.CmsXmlDynamicFunctionHandler;
053import org.opencms.xml.containerpage.I_CmsFormatterBean;
054import org.opencms.xml.content.CmsXmlContent;
055import org.opencms.xml.content.CmsXmlContentFactory;
056import org.opencms.xml.content.CmsXmlContentProperty;
057import org.opencms.xml.content.CmsXmlContentProperty.Visibility;
058import org.opencms.xml.content.CmsXmlContentRootLocation;
059import org.opencms.xml.content.I_CmsXmlContentLocation;
060import org.opencms.xml.content.I_CmsXmlContentValueLocation;
061import org.opencms.xml.types.CmsXmlVarLinkValue;
062import org.opencms.xml.types.CmsXmlVfsFileValue;
063
064import java.util.ArrayList;
065import java.util.HashSet;
066import java.util.LinkedHashSet;
067import java.util.List;
068import java.util.Locale;
069import java.util.Set;
070
071import org.apache.commons.logging.Log;
072
073import com.google.common.collect.Lists;
074
075/**
076 * A class to parse ADE sitemap or module configuration files and create configuration objects from them.<p>
077 */
078public class CmsConfigurationReader {
079
080    /** The default locale for configuration objects. */
081    public static final Locale DEFAULT_LOCALE = CmsLocaleManager.getLocale("en");
082
083    /** Node name for added formatters. */
084    public static final String N_ADD_FORMATTER = "AddFormatter";
085
086    /** Node name for the nested content with the added formatters. */
087    public static final String N_ADD_FORMATTERS = "AddFormatters";
088
089    /** The create content locally node name. */
090    public static final String N_CREATE_CONTENTS_LOCALLY = "CreateContentsLocally";
091
092    /** The default node name. */
093    public static final String N_DEFAULT = "Default";
094
095    /** The description node name. */
096    public static final String N_DESCRIPTION = "Description";
097
098    /** The detail page node name. */
099    public static final String N_DETAIL_PAGE = "DetailPage";
100
101    /** The detail pages disabled node name. */
102    public static final String N_DETAIL_PAGES_DISABLED = "DetailPagesDisabled";
103
104    /** The disabled node name. */
105    public static final String N_DISABLED = "Disabled";
106
107    /** The discard model pages node name. */
108    public static final String N_DISCARD_MODEL_PAGES = "DiscardModelPages";
109
110    /** The discard properties node name. */
111    public static final String N_DISCARD_PROPERTIES = "DiscardProperties";
112
113    /** The discard types node name. */
114    public static final String N_DISCARD_TYPES = "DiscardTypes";
115
116    /** The display name node name. */
117    public static final String N_DISPLAY_NAME = "DisplayName";
118
119    /** The element view node name. */
120    public static final String N_ELEMENT_VIEW = "ElementView";
121
122    /** The error node name. */
123    public static final String N_ERROR = "Error";
124
125    /** The folder node name. */
126    public static final String N_FOLDER = "Folder";
127
128    /** The formatter node name. */
129    public static final String N_FORMATTER = "Formatter";
130
131    /** The function node name. */
132    public static final String N_FUNCTION = "Function";
133
134    /** The function node name. */
135    public static final String N_FUNCTION_DEFAULT_PAGE = "FunctionDefaultPage";
136
137    /** The function reference node name. */
138    public static final String N_FUNCTION_REF = "FunctionRef";
139
140    /** The is default node name. */
141    public static final String N_IS_DEFAULT = "IsDefault";
142
143    /** The is preview node name. */
144    public static final String N_IS_PREVIEW = "IsPreview";
145
146    /** The JSP node name. */
147    public static final String N_JSP = "Jsp";
148
149    /** The localization node name. */
150    public static final String N_LOCALIZATION = "Localization";
151
152    /** The master configuration node name. */
153    public static final String N_MASTER_CONFIG = "MasterConfig";
154
155    /** The max width node name. */
156    public static final String N_MAX_WIDTH = "MaxWidth";
157
158    /** The min width node name. */
159    public static final String N_MIN_WIDTH = "MinWidth";
160
161    /** The model page node name. */
162    public static final String N_MODEL_PAGE = "ModelPage";
163
164    /** The folder name node name. */
165    public static final String N_NAME = "Name";
166
167    /** The name pattern node name. */
168    public static final String N_NAME_PATTERN = "NamePattern";
169
170    /** The order node name. */
171    public static final String N_ORDER = "Order";
172
173    /** The page node name. */
174    public static final String N_PAGE = "Page";
175
176    /** The folder path node name. */
177    public static final String N_PATH = "Path";
178
179    /** The  PreferDetailPagesForLocalContents node name. */
180    public static final String N_PREFER_DETAIL_PAGES_FOR_LOCAL_CONTENTS = "PreferDetailPagesForLocalContents";
181
182    /** The prefer folder node name. */
183    public static final String N_PREFER_FOLDER = "PreferFolder";
184
185    /** The property node name. */
186    public static final String N_PROPERTY = "Property";
187
188    /** The property name node name. */
189    public static final String N_PROPERTY_NAME = "PropertyName";
190
191    /** Node name for the "Remove all formatters"-option. */
192    public static final String N_REMOVE_ALL_FORMATTERS = "RemoveAllFormatters";
193
194    /** Node name for removed formatters. */
195    public static final String N_REMOVE_FORMATTER = "RemoveFormatter";
196
197    /** Node name for the nested content with the removed formatters. */
198    public static final String N_REMOVE_FORMATTERS = "RemoveFormatters";
199
200    /** The resource type node name. */
201    public static final String N_RESOURCE_TYPE = "ResourceType";
202
203    /** The regex rule node name. */
204    public static final String N_RULE_REGEX = "RuleRegex";
205
206    /** The rule type node name. */
207    public static final String N_RULE_TYPE = "RuleType";
208
209    /** The type node name. */
210    public static final String N_TYPE = "Type";
211
212    /** The type name node name. */
213    public static final String N_TYPE_NAME = "TypeName";
214
215    /** The widget node name. */
216    public static final String N_VISIBILITY = "Visibility";
217
218    /** The widget node name. */
219    public static final String N_WIDGET = "Widget";
220
221    /** The widget configuration node name. */
222    public static final String N_WIDGET_CONFIG = "WidgetConfig";
223
224    /** Scheme for explorer type view links. */
225    public static final String VIEW_SCHEME = "view://";
226
227    /** The log object for this class. */
228    private static final Log LOG = CmsLog.getLog(CmsConfigurationReader.class);
229
230    /** The CopyInModels node name. */
231    private static final String N_COPY_IN_MODELS = "CopyInModels";
232
233    /** The ElementDeleteMode node name. */
234    private static final String N_ELEMENT_DELETE_MODE = "ElementDeleteMode";
235
236    /** The IncludeName node name. */
237    private static final String N_INCLUDE_NAME = "IncludeName";
238
239    /** The PageRelative node name. */
240    private static final String N_PAGE_RELATIVE = "PageRelative";
241
242    /** The ShowInDefaultView node name. */
243    private static final String N_SHOW_IN_DEFAULT_VIEW = "ShowInDefaultView";
244
245    /** The CMS context used for reading the configuration data. */
246    private CmsObject m_cms;
247
248    /** The parsed detail page configuration elements. */
249    private List<CmsDetailPageInfo> m_detailPageConfigs = new ArrayList<CmsDetailPageInfo>();
250
251    /** The list of configured function references. */
252    private List<CmsFunctionReference> m_functionReferences = new ArrayList<CmsFunctionReference>();
253
254    /** The parsed model page configuration elements. */
255    private List<CmsModelPageConfig> m_modelPageConfigs = new ArrayList<CmsModelPageConfig>();
256
257    /** The parsed property configuration elements. */
258    private List<CmsPropertyConfig> m_propertyConfigs = new ArrayList<CmsPropertyConfig>();
259
260    /** The resource type configuration objects. */
261    private List<CmsResourceTypeConfig> m_resourceTypeConfigs = new ArrayList<CmsResourceTypeConfig>();
262
263    /**
264     * Creates a new configuration reader.<p>
265     *
266     * @param cms the CMS context which should be used to read the configuration data.<p>
267     */
268    public CmsConfigurationReader(CmsObject cms) {
269
270        m_cms = cms;
271    }
272
273    /**
274     * Gets the string value of an XML content location.<p>
275     *
276     * @param cms the CMS context to use
277     * @param location an XML content location
278     *
279     * @return the string value of that XML content location
280     */
281    public static String getString(CmsObject cms, I_CmsXmlContentValueLocation location) {
282
283        if (location == null) {
284            return null;
285        }
286        return location.asString(cms);
287    }
288
289    /**
290     * Helper method to parse a property.<p>
291     *
292     * @param cms the CMS context to use
293     * @param field the location of the parent value
294     *
295     * @return the parsed property configuration
296     */
297    public static CmsPropertyConfig parseProperty(CmsObject cms, I_CmsXmlContentLocation field) {
298
299        String name = getString(cms, field.getSubValue(N_PROPERTY_NAME));
300        String includeName = getString(cms, field.getSubValue(N_INCLUDE_NAME));
301        String widget = getString(cms, field.getSubValue(N_WIDGET));
302        String widgetConfig = getString(cms, field.getSubValue(N_WIDGET_CONFIG));
303        String ruleRegex = getString(cms, field.getSubValue(N_RULE_REGEX));
304        String ruleType = getString(cms, field.getSubValue(N_RULE_TYPE));
305        String default1 = getString(cms, field.getSubValue(N_DEFAULT));
306        String error = getString(cms, field.getSubValue(N_ERROR));
307        String niceName = getString(cms, field.getSubValue(N_DISPLAY_NAME));
308        String description = getString(cms, field.getSubValue(N_DESCRIPTION));
309        String preferFolder = getString(cms, field.getSubValue(N_PREFER_FOLDER));
310
311        String disabledStr = getString(cms, field.getSubValue(N_DISABLED));
312        boolean disabled = ((disabledStr != null) && Boolean.parseBoolean(disabledStr));
313
314        String orderStr = getString(cms, field.getSubValue(N_ORDER));
315        int order = I_CmsConfigurationObject.DEFAULT_ORDER;
316
317        try {
318            order = Integer.parseInt(orderStr);
319        } catch (NumberFormatException e) {
320            // noop
321        }
322
323        Visibility visibility;
324        String visibilityStr = getString(cms, field.getSubValue(N_VISIBILITY));
325        try {
326            // to stay compatible with former visibility option values
327            if ("both".equals(visibilityStr)) {
328                visibilityStr = Visibility.elementAndParentIndividual.name();
329            } else if ("parent".equals(visibilityStr)) {
330                visibilityStr = Visibility.parentShared.name();
331            }
332            visibility = Visibility.valueOf(visibilityStr);
333        } catch (Exception e) {
334            visibility = null;
335        }
336        CmsXmlContentProperty prop = new CmsXmlContentProperty(
337            name,
338            "string",
339            visibility,
340            widget,
341            widgetConfig,
342            ruleRegex,
343            ruleType,
344            default1,
345            niceName,
346            description,
347            error,
348            preferFolder).withIncludeName(includeName);
349        // since these are real properties, using type vfslist makes no sense, so we always use the "string" type
350        CmsPropertyConfig propConfig = new CmsPropertyConfig(prop, disabled, order);
351        return propConfig;
352
353    }
354
355    /**
356     * Returns the list of function references.<p>
357     *
358     * @return the list of function references
359     */
360    public List<CmsFunctionReference> getFunctionReferences() {
361
362        return new ArrayList<CmsFunctionReference>(m_functionReferences);
363    }
364
365    /**
366     * Returns the modelPageConfigs.<p>
367     *
368     * @return the modelPageConfigs
369     */
370    public List<CmsModelPageConfig> getModelPageConfigs() {
371
372        return m_modelPageConfigs;
373    }
374
375    /**
376     * Parses the formatters to add.<p>
377     *
378     * @param node the parent node
379     * @return the set of keys of the formatters to add
380     */
381    public Set<String> parseAddFormatters(I_CmsXmlContentLocation node) {
382
383        Set<String> addFormatters = new HashSet<String>();
384        for (I_CmsXmlContentValueLocation addLoc : node.getSubValues(N_ADD_FORMATTERS + "/" + N_ADD_FORMATTER)) {
385            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)addLoc.getValue();
386            CmsLink link = value.getLink(m_cms);
387            if (link != null) {
388                addFormatters.add(link.getStructureId().toString());
389            }
390        }
391        return addFormatters;
392    }
393
394    /**
395     * Parses a configuration XML content and creates a configuration object from it.<p>
396     *
397     * @param basePath the base path
398     * @param content the XML content
399     *
400     * @return the created configuration object with the data from the XML content
401     * @throws CmsException if something goes wrong
402     */
403    public CmsADEConfigDataInternal parseConfiguration(String basePath, CmsXmlContent content) throws CmsException {
404
405        m_detailPageConfigs = Lists.newArrayList();
406        m_functionReferences = Lists.newArrayList();
407        m_modelPageConfigs = Lists.newArrayList();
408        m_propertyConfigs = Lists.newArrayList();
409        m_resourceTypeConfigs = Lists.newArrayList();
410
411        if (!content.hasLocale(DEFAULT_LOCALE)) {
412            return CmsADEConfigDataInternal.emptyConfiguration(basePath);
413        }
414        CmsXmlContentRootLocation root = new CmsXmlContentRootLocation(content, DEFAULT_LOCALE);
415        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_RESOURCE_TYPE)) {
416            try {
417                parseResourceTypeConfig(basePath, node);
418            } catch (CmsException e) {
419                LOG.warn(e.getLocalizedMessage(), e);
420            }
421        }
422        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_MODEL_PAGE)) {
423            try {
424                parseModelPage(node);
425            } catch (CmsException e) {
426                LOG.warn(e.getLocalizedMessage(), e);
427            }
428        }
429        for (I_CmsXmlContentLocation node : root.getSubValues(N_PROPERTY)) {
430            parseProperty(node);
431        }
432        for (I_CmsXmlContentLocation node : root.getSubValues(N_DETAIL_PAGE)) {
433            try {
434                parseDetailPage(node);
435            } catch (CmsException e) {
436                LOG.warn(e.getLocalizedMessage(), e);
437            }
438        }
439
440        for (I_CmsXmlContentLocation node : root.getSubValues(N_FUNCTION_REF)) {
441            parseFunctionReference(node);
442        }
443
444        boolean removeAllFormatters = getBoolean(root, N_REMOVE_ALL_FORMATTERS);
445        CmsFormatterChangeSet formatterChangeSet = parseFormatterChangeSet(basePath, root, removeAllFormatters);
446        boolean discardInheritedTypes = getBoolean(root, N_DISCARD_TYPES);
447        boolean discardInheritedProperties = getBoolean(root, N_DISCARD_PROPERTIES);
448        boolean discardInheritedModelPages = getBoolean(root, N_DISCARD_MODEL_PAGES);
449
450        boolean createContentsLocally = getBoolean(root, N_CREATE_CONTENTS_LOCALLY);
451        boolean preferDetailPagesForLocalContents = getBoolean(root, N_PREFER_DETAIL_PAGES_FOR_LOCAL_CONTENTS);
452
453        boolean isModuleConfig = OpenCms.getResourceManager().getResourceType(
454            content.getFile().getTypeId()).getTypeName().equals(CmsADEManager.MODULE_CONFIG_TYPE);
455
456        String masterConfig = getString(root.getSubValue(N_MASTER_CONFIG));
457        CmsResource masterConfigResource = null;
458        if (masterConfig != null) {
459            masterConfigResource = m_cms.readResource(masterConfig, CmsResourceFilter.IGNORE_EXPIRATION);
460        }
461        Set<CmsUUID> functions = new LinkedHashSet<>();
462        for (I_CmsXmlContentValueLocation node : root.getSubValues(N_FUNCTION)) {
463            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)node.getValue();
464            CmsLink link = value.getLink(m_cms);
465            if (link != null) {
466                functions.add(link.getStructureId());
467            }
468        }
469        if (functions.isEmpty()) {
470            functions = null;
471        }
472
473        CmsADEConfigDataInternal result = new CmsADEConfigDataInternal(
474            content.getFile(),
475            isModuleConfig,
476            basePath,
477            masterConfigResource,
478            m_resourceTypeConfigs,
479            discardInheritedTypes,
480            m_propertyConfigs,
481            discardInheritedProperties,
482            m_detailPageConfigs,
483            m_modelPageConfigs,
484            m_functionReferences,
485            discardInheritedModelPages,
486            createContentsLocally,
487            preferDetailPagesForLocalContents,
488            formatterChangeSet,
489            functions);
490        return result;
491    }
492
493    /**
494     * Parses a folder which may either be given as a path or as a folder name.<p>
495     *
496     * @param basePath the  base path for the configuration
497     * @param location the XML content node from which to parse the folder
498     * @return the folder bean
499     *
500     * @throws CmsException if something goes wrong
501     */
502    public CmsContentFolderDescriptor parseFolderOrName(String basePath, I_CmsXmlContentLocation location)
503    throws CmsException {
504
505        if (location == null) {
506            return null;
507        }
508        I_CmsXmlContentValueLocation nameLoc = location.getSubValue(N_NAME);
509        I_CmsXmlContentValueLocation pathLoc = location.getSubValue(N_PATH);
510        I_CmsXmlContentValueLocation pageRelativeLoc = location.getSubValue(N_PAGE_RELATIVE);
511        if (nameLoc != null) {
512            String name = nameLoc.asString(m_cms);
513            return new CmsContentFolderDescriptor(
514                basePath == null ? null : CmsStringUtil.joinPaths(basePath, CmsADEManager.CONTENT_FOLDER_NAME),
515                name);
516        } else if (pathLoc != null) {
517            String path = pathLoc.asString(m_cms);
518            CmsResource folder = m_cms.readResource(path);
519            return new CmsContentFolderDescriptor(folder);
520        } else if (pageRelativeLoc != null) {
521            return CmsContentFolderDescriptor.createPageRelativeFolderDescriptor();
522        } else {
523            return null;
524        }
525    }
526
527    /**
528     * Parses a formatter bean.<p>
529     *
530     * @param typeName the type name for which the formatter is being parsed
531     * @param node the node from which to parse the formatter data
532     *
533     * @return the formatter bean from the XML
534     */
535    public CmsFormatterBean parseFormatter(String typeName, I_CmsXmlContentLocation node) {
536
537        String type = getString(node.getSubValue(N_TYPE));
538        String minWidth = getString(node.getSubValue(N_MIN_WIDTH));
539        String maxWidth = getString(node.getSubValue(N_MAX_WIDTH));
540        boolean preview = false;
541        I_CmsXmlContentValueLocation previewLoc = node.getSubValue(N_IS_PREVIEW);
542        preview = (previewLoc != null) && Boolean.parseBoolean(previewLoc.asString(m_cms));
543        String jsp = m_cms.getRequestContext().addSiteRoot(getString(node.getSubValue(N_JSP)));
544        boolean searchContent = true;
545        CmsFormatterBean formatterBean = new CmsFormatterBean(
546            type,
547            jsp,
548            minWidth,
549            maxWidth,
550            "" + preview,
551            "" + searchContent,
552            null);
553        return formatterBean;
554
555    }
556
557    /**
558     * Parses model page data from the XML content.<p>
559     *
560     * @param node the XML content node
561     * @throws CmsException if something goes wrong
562     */
563    public void parseModelPage(I_CmsXmlContentLocation node) throws CmsException {
564
565        String page = getString(node.getSubValue(N_PAGE));
566        I_CmsXmlContentValueLocation disabledLoc = node.getSubValue(N_DISABLED);
567        boolean disabled = (disabledLoc != null) && Boolean.parseBoolean(disabledLoc.asString(m_cms));
568        I_CmsXmlContentValueLocation defaultLoc = node.getSubValue(N_IS_DEFAULT);
569        boolean isDefault = (defaultLoc != null) && Boolean.parseBoolean(defaultLoc.asString(m_cms));
570        CmsModelPageConfig modelPage = new CmsModelPageConfig(m_cms.readResource(page), isDefault, disabled);
571        m_modelPageConfigs.add(modelPage);
572
573    }
574
575    /**
576     * Parses the set of formatters to remove.<p>
577     *
578     * @param node the parent node
579     * @return the set of formatters to remove
580     */
581    public Set<String> parseRemoveFormatters(I_CmsXmlContentLocation node) {
582
583        Set<String> removeFormatters = new HashSet<String>();
584        for (I_CmsXmlContentValueLocation removeLoc : node.getSubValues(
585            N_REMOVE_FORMATTERS + "/" + N_REMOVE_FORMATTER)) {
586            CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)removeLoc.getValue();
587            CmsLink link = value.getLink(m_cms);
588            if (link != null) {
589                removeFormatters.add(link.getStructureId().toString());
590            }
591        }
592        return removeFormatters;
593    }
594
595    /**
596     * Parses a resource type configuration element from the XML content.<p>
597     *
598     * @param basePath the base path of the configuration
599     * @param node the XML configuration node
600     * @throws CmsException if something goes wrong
601     */
602    public void parseResourceTypeConfig(String basePath, I_CmsXmlContentLocation node) throws CmsException {
603
604        I_CmsXmlContentValueLocation typeNameLoc = node.getSubValue(N_TYPE_NAME);
605        String typeName = typeNameLoc.asString(m_cms);
606        CmsContentFolderDescriptor folderOrName = parseFolderOrName(basePath, node.getSubValue(N_FOLDER));
607        I_CmsXmlContentValueLocation disabledLoc = node.getSubValue(N_DISABLED);
608        boolean disabled = false;
609        boolean addDisabled = false;
610        boolean createDisabled = false;
611        String disabledStr = disabledLoc == null ? null : disabledLoc.asString(m_cms);
612        if ((disabledStr != null) && "add".equalsIgnoreCase(disabledStr.trim())) {
613            addDisabled = true;
614        } else if ((disabledStr != null) && "create".equalsIgnoreCase(disabledStr.trim())) {
615            createDisabled = true;
616        } else {
617            disabled = Boolean.parseBoolean(disabledStr);
618        }
619        I_CmsXmlContentValueLocation namePatternLoc = node.getSubValue(N_NAME_PATTERN);
620        String namePattern = null;
621        if (namePatternLoc != null) {
622            namePattern = namePatternLoc.asString(m_cms);
623        }
624
625        boolean detailPagesDisabled = false;
626        I_CmsXmlContentValueLocation detailDisabledLoc = node.getSubValue(N_DETAIL_PAGES_DISABLED);
627        if (detailDisabledLoc != null) {
628            String detailPagesDisabledStr = detailDisabledLoc.asString(m_cms);
629            detailPagesDisabled = Boolean.parseBoolean(detailPagesDisabledStr);
630        }
631
632        int order = I_CmsConfigurationObject.DEFAULT_ORDER;
633        I_CmsXmlContentValueLocation orderLoc = node.getSubValue(N_ORDER);
634        if (orderLoc != null) {
635            try {
636                String orderStr = orderLoc.asString(m_cms);
637                order = Integer.parseInt(orderStr);
638            } catch (NumberFormatException e) {
639                // noop
640            }
641        }
642
643        I_CmsXmlContentValueLocation elementViewLoc = node.getSubValue(N_ELEMENT_VIEW);
644        CmsUUID elementView = null;
645        if (elementViewLoc != null) {
646            try {
647                CmsXmlVarLinkValue elementViewValue = (CmsXmlVarLinkValue)elementViewLoc.getValue();
648                String stringValue = elementViewValue.getStringValue(m_cms);
649                if ("".equals(stringValue)) {
650                    elementView = CmsUUID.getNullUUID();
651                } else if (stringValue.startsWith(VIEW_SCHEME)) {
652                    elementView = new CmsUUID(stringValue.substring(VIEW_SCHEME.length()));
653                } else {
654                    elementView = elementViewValue.getLink(m_cms).getStructureId();
655                }
656            } catch (Exception e) {
657                // in case parsing the link fails, the default element view will be used
658            }
659        }
660
661        I_CmsXmlContentValueLocation locationLoc = node.getSubValue(N_LOCALIZATION);
662        String localization = null;
663        if (locationLoc != null) {
664            CmsXmlVfsFileValue locationValue = (CmsXmlVfsFileValue)locationLoc.getValue();
665            CmsLink link = locationValue.getLink(m_cms);
666            if (null != link) {
667                String stringValue = link.getSitePath(m_cms);
668                // extract bundle base name from the path to the bundle file
669                int lastSlashIndex = stringValue.lastIndexOf("/");
670                String fileName = stringValue.substring(lastSlashIndex + 1);
671                if (CmsFileUtil.getExtension(fileName).equals(".properties")) {
672                    fileName = fileName.substring(0, fileName.length() - ".properties".length());
673                }
674                String localeSuffix = CmsStringUtil.getLocaleSuffixForName(fileName);
675                if ((localeSuffix != null) && fileName.endsWith(localeSuffix)) {
676                    fileName = fileName.substring(0, fileName.length() - localeSuffix.length() - 1);
677                }
678                localization = fileName;
679            }
680        }
681
682        I_CmsXmlContentValueLocation showDefaultViewLoc = node.getSubValue(N_SHOW_IN_DEFAULT_VIEW);
683        Boolean showInDefaultView = null;
684        if (showDefaultViewLoc != null) {
685            showInDefaultView = Boolean.valueOf(
686                Boolean.parseBoolean(showDefaultViewLoc.getValue().getStringValue(m_cms)));
687        }
688
689        I_CmsXmlContentValueLocation copyInModelsLoc = node.getSubValue(N_COPY_IN_MODELS);
690        Boolean copyInModels = null;
691        if (copyInModelsLoc != null) {
692            copyInModels = Boolean.valueOf(Boolean.parseBoolean(copyInModelsLoc.getValue().getStringValue(m_cms)));
693        }
694
695        I_CmsXmlContentValueLocation elementDeleteModeLoc = node.getSubValue(N_ELEMENT_DELETE_MODE);
696        ElementDeleteMode elementDeleteMode = null;
697        if (elementDeleteModeLoc != null) {
698            try {
699                elementDeleteMode = ElementDeleteMode.valueOf(elementDeleteModeLoc.getValue().getStringValue(m_cms));
700            } catch (Exception e) {
701                LOG.warn(e.getLocalizedMessage(), e);
702            }
703        }
704
705        List<I_CmsFormatterBean> formatters = new ArrayList<I_CmsFormatterBean>();
706        for (I_CmsXmlContentValueLocation formatterLoc : node.getSubValues(N_FORMATTER)) {
707            CmsFormatterBean formatter = parseFormatter(typeName, formatterLoc);
708            formatters.add(formatter);
709        }
710
711        CmsResourceTypeConfig typeConfig = new CmsResourceTypeConfig(
712            typeName,
713            disabled,
714            folderOrName,
715            namePattern,
716            detailPagesDisabled,
717            addDisabled,
718            createDisabled,
719            elementView,
720            localization,
721            showInDefaultView,
722            copyInModels,
723            order,
724            elementDeleteMode);
725        m_resourceTypeConfigs.add(typeConfig);
726    }
727
728    /**
729     * Parses the sitemap configuration given the configuration file and base path.<p>
730     *
731     * @param basePath the base path
732     * @param configRes the configuration file resource
733     * @return the parsed configuration data
734     * @throws CmsException if something goes wrong
735     */
736    public CmsADEConfigDataInternal parseSitemapConfiguration(String basePath, CmsResource configRes)
737    throws CmsException {
738
739        LOG.info("Parsing configuration " + configRes.getRootPath());
740        CmsFile configFile = m_cms.readFile(configRes);
741        CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, configFile);
742        return parseConfiguration(basePath, content);
743    }
744
745    /**
746     * Reads the configurations of all modules and combines them into a single configuration object.<p>
747     *
748     * @return the combined configuration object
749     */
750    public List<CmsADEConfigDataInternal> readModuleConfigurations() {
751
752        List<CmsADEConfigDataInternal> configurations = new ArrayList<CmsADEConfigDataInternal>();
753        List<CmsModule> modules = OpenCms.getModuleManager().getAllInstalledModules();
754        long beginTime = System.currentTimeMillis();
755        for (CmsModule module : modules) {
756            String configPath = module.getConfigurationPath();
757            if (m_cms.existsResource(configPath)) {
758                try {
759                    CmsResource configFile = m_cms.readResource(configPath);
760                    LOG.info("Found module configuration " + configPath + " for module " + module.getName());
761                    CmsADEConfigDataInternal config = parseSitemapConfiguration(null, configFile);
762                    configurations.add(config);
763                } catch (CmsException e) {
764                    // errors while parsing configuration
765                    LOG.error(e.getLocalizedMessage(), e);
766                } catch (CmsRuntimeException e) {
767                    // may happen during import of org.opencms.ade.configuration module
768                    LOG.warn(e.getLocalizedMessage(), e);
769                } catch (Throwable e) {
770                    LOG.error(e.getLocalizedMessage(), e);
771                }
772            }
773        }
774        long endTime = System.currentTimeMillis();
775        LOG.debug("readModuleConfiguations took " + (endTime - beginTime) + "ms");
776        return configurations;
777    }
778
779    /**
780     * Helper method to read a boolean value from the XML.<p>
781     *
782     * If the element is not found in the XML, false is returned.<p>
783     *
784     * @param parent the parent node
785     * @param name the name of the XML content value
786     * @return the boolean value
787     */
788    protected boolean getBoolean(I_CmsXmlContentLocation parent, String name) {
789
790        I_CmsXmlContentValueLocation location = parent.getSubValue(name);
791        if (location == null) {
792            return false;
793        }
794        String value = location.getValue().getStringValue(m_cms);
795        return Boolean.parseBoolean(value);
796    }
797
798    /**
799     * Gets the string value of an XML content location.<p>
800     *
801     * @param location an XML content location
802     *
803     * @return the string value of that XML content location
804     */
805    protected String getString(I_CmsXmlContentValueLocation location) {
806
807        return getString(m_cms, location);
808    }
809
810    /**
811     * Parses the detail pages from an XML content node.<p>
812     *
813     * @param node the XML content node
814     *
815     * @throws CmsException if something goes wrong
816     */
817    protected void parseDetailPage(I_CmsXmlContentLocation node) throws CmsException {
818
819        I_CmsXmlContentValueLocation pageLoc = node.getSubValue(N_PAGE);
820        String typeName = getString(node.getSubValue(N_TYPE));
821        try {
822            String page = pageLoc.asString(m_cms);
823            CmsResource detailPageRes = m_cms.readResource(page);
824            CmsUUID id = detailPageRes.getStructureId();
825            String iconClasses;
826            if (typeName.startsWith(CmsDetailPageInfo.FUNCTION_PREFIX)) {
827                iconClasses = CmsIconUtil.getIconClasses(CmsXmlDynamicFunctionHandler.TYPE_FUNCTION, null, false);
828            } else {
829                iconClasses = CmsIconUtil.getIconClasses(typeName, null, false);
830            }
831
832            CmsDetailPageInfo detailPage = new CmsDetailPageInfo(id, page, typeName, iconClasses);
833            m_detailPageConfigs.add(detailPage);
834        } catch (CmsVfsResourceNotFoundException e) {
835            CmsUUID structureId = pageLoc.asId(m_cms);
836            CmsResource detailPageRes = m_cms.readResource(structureId);
837            CmsDetailPageInfo detailPage = new CmsDetailPageInfo(
838                structureId,
839                m_cms.getSitePath(detailPageRes),
840                typeName,
841                CmsIconUtil.getIconClasses(typeName, null, false));
842            m_detailPageConfigs.add(detailPage);
843        }
844    }
845
846    /**
847     * Parses the formatter change set.<p>
848     *
849     * @param basePath the configuration base path
850     * @param node the parent node
851     * @param removeAllFormatters flag, indicating if all formatters that are not explicitly added should be removed
852     * @return the formatter change set
853     */
854    protected CmsFormatterChangeSet parseFormatterChangeSet(
855        String basePath,
856        I_CmsXmlContentLocation node,
857        boolean removeAllFormatters) {
858
859        Set<String> addFormatters = parseAddFormatters(node);
860        addFormatters.addAll(readLocalFormatters(node));
861        Set<String> removeFormatters = removeAllFormatters ? new HashSet<String>() : parseRemoveFormatters(node);
862        String siteRoot = null;
863        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(basePath)) {
864            siteRoot = OpenCms.getSiteManager().getSiteRoot(basePath);
865        }
866        CmsFormatterChangeSet result = new CmsFormatterChangeSet(
867            removeFormatters,
868            addFormatters,
869            siteRoot,
870            removeAllFormatters);
871        return result;
872    }
873
874    /**
875     * Parses a function reference node.<p>
876     *
877     * @param node the function reference node
878     */
879    protected void parseFunctionReference(I_CmsXmlContentLocation node) {
880
881        String name = node.getSubValue(N_NAME).asString(m_cms);
882        CmsUUID functionId = node.getSubValue(N_FUNCTION).asId(m_cms);
883        CmsUUID functionDefaultPageId = null;
884        I_CmsXmlContentValueLocation defaultPageValue = node.getSubValue(N_FUNCTION_DEFAULT_PAGE);
885        if (defaultPageValue != null) {
886            functionDefaultPageId = defaultPageValue.asId(m_cms);
887        }
888        I_CmsXmlContentValueLocation orderNode = node.getSubValue(N_ORDER);
889        int order = I_CmsConfigurationObject.DEFAULT_ORDER;
890        if (orderNode != null) {
891            String orderStr = orderNode.asString(m_cms);
892            try {
893                order = Integer.parseInt(orderStr);
894            } catch (NumberFormatException e) {
895                // noop
896            }
897        }
898        m_functionReferences.add(new CmsFunctionReference(name, functionId, functionDefaultPageId, order));
899    }
900
901    /**
902     * Parses a single field definition from a content value.<p>
903     *
904     * @param field the content value to parse the field from
905     */
906    private void parseProperty(I_CmsXmlContentLocation field) {
907
908        CmsPropertyConfig propConfig = parseProperty(m_cms, field);
909        m_propertyConfigs.add(propConfig);
910    }
911
912    /**
913     * Reads the local macro or flex formatters from the .formatters folder if present.<p>
914     *
915     * @param node the xml content node
916     *
917     * @return the local formatters
918     */
919    private Set<String> readLocalFormatters(I_CmsXmlContentLocation node) {
920
921        Set<String> addFormatters = new HashSet<String>();
922        String path = m_cms.getSitePath(node.getDocument().getFile());
923        path = CmsStringUtil.joinPaths(CmsResource.getParentFolder(path), ".formatters");
924        try {
925            if (m_cms.existsResource(path, CmsResourceFilter.IGNORE_EXPIRATION)) {
926                I_CmsResourceType macroType = OpenCms.getResourceManager().getResourceType(
927                    CmsFormatterConfigurationCache.TYPE_MACRO_FORMATTER);
928                CmsResourceFilter filter = CmsResourceFilter.IGNORE_EXPIRATION.addRequireType(macroType);
929                List<CmsResource> macroFormatters = m_cms.readResources(path, filter);
930                for (CmsResource formatter : macroFormatters) {
931                    addFormatters.add(formatter.getStructureId().toString());
932                }
933                I_CmsResourceType flexType = OpenCms.getResourceManager().getResourceType(
934                    CmsFormatterConfigurationCache.TYPE_FLEX_FORMATTER);
935                CmsResourceFilter filterFlex = CmsResourceFilter.IGNORE_EXPIRATION.addRequireType(flexType);
936                List<CmsResource> flexFormatters = m_cms.readResources(path, filterFlex);
937                for (CmsResource formatter : flexFormatters) {
938                    addFormatters.add(formatter.getStructureId().toString());
939                }
940            }
941        } catch (CmsException e) {
942            LOG.warn(e.getMessage(), e);
943        }
944        return addFormatters;
945    }
946
947}