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.contenteditor;
029
030import org.opencms.acacia.shared.CmsAttributeConfiguration;
031import org.opencms.acacia.shared.CmsTabInfo;
032import org.opencms.acacia.shared.CmsType;
033import org.opencms.ade.contenteditor.CmsWidgetUtil.WidgetInfo;
034import org.opencms.ade.contenteditor.shared.CmsComplexWidgetData;
035import org.opencms.ade.contenteditor.shared.CmsExternalWidgetConfiguration;
036import org.opencms.file.CmsFile;
037import org.opencms.file.CmsObject;
038import org.opencms.file.CmsRequestContext;
039import org.opencms.i18n.CmsMessages;
040import org.opencms.i18n.CmsMultiMessages;
041import org.opencms.jsp.util.CmsKeyDummyMacroResolver;
042import org.opencms.main.CmsLog;
043import org.opencms.main.OpenCms;
044import org.opencms.util.CmsMacroResolver;
045import org.opencms.util.CmsStringUtil;
046import org.opencms.util.I_CmsMacroResolver;
047import org.opencms.widgets.A_CmsWidget;
048import org.opencms.widgets.I_CmsADEWidget;
049import org.opencms.widgets.I_CmsComplexWidget;
050import org.opencms.widgets.I_CmsWidget;
051import org.opencms.xml.CmsXmlContentDefinition;
052import org.opencms.xml.CmsXmlException;
053import org.opencms.xml.content.CmsDefaultXmlContentHandler;
054import org.opencms.xml.content.CmsXmlContentTab;
055import org.opencms.xml.content.I_CmsXmlContentHandler;
056import org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType;
057import org.opencms.xml.types.A_CmsXmlContentValue;
058import org.opencms.xml.types.CmsXmlAccessRestrictionValue;
059import org.opencms.xml.types.CmsXmlDynamicCategoryValue;
060import org.opencms.xml.types.CmsXmlNestedContentDefinition;
061import org.opencms.xml.types.I_CmsXmlSchemaType;
062
063import java.util.ArrayList;
064import java.util.Collection;
065import java.util.Collections;
066import java.util.HashMap;
067import java.util.List;
068import java.util.Locale;
069import java.util.Map;
070
071import org.apache.commons.logging.Log;
072
073/**
074 * Visitor to read all types and attribute configurations within a content definition.<p>
075 */
076public class CmsContentTypeVisitor {
077
078    /**
079     * Helper class to evaluate the widget display type.<p>
080     */
081    protected static class DisplayTypeEvaluator {
082
083        /** The attribute name. */
084        private String m_attributeName;
085
086        /** The attribute type configuration. */
087        private CmsAttributeConfiguration m_config;
088
089        /** The configured display type. */
090        private DisplayType m_configuredType;
091
092        /** The default display type. */
093        private DisplayType m_default;
094
095        /** The applied rule. */
096        private EvaluationRule m_rule;
097
098        /**
099         * Constructor.<p>
100         *
101         * @param config the attribute type configuration
102         * @param configuredType the configured display type
103         * @param defaultType the default display type
104         * @param rule the applied rule
105         */
106        protected DisplayTypeEvaluator(
107            CmsAttributeConfiguration config,
108            DisplayType configuredType,
109            DisplayType defaultType,
110            EvaluationRule rule) {
111
112            m_config = config;
113
114            m_configuredType = configuredType;
115            m_default = defaultType;
116            m_rule = rule;
117        }
118
119        /**
120         * Returns the attribute name.<p>
121         *
122         * @return the attribute name
123         */
124        protected String getAttributeName() {
125
126            return m_attributeName;
127        }
128
129        /**
130         * Returns the attribute configuration with the evaluated display type.<p>
131         *
132         * @param predecessor the proposed predecessor display type
133         * @param successor the proposed successor display type
134         *
135         * @return the attribute configuration
136         */
137        protected CmsAttributeConfiguration getEvaluatedConfiguration(DisplayType predecessor, DisplayType successor) {
138
139            DisplayType resultingType = m_configuredType;
140
141            if (resultingType.equals(DisplayType.none)) {
142                if (m_rule.equals(EvaluationRule.rootLevel)) {
143                    resultingType = DisplayType.wide;
144                } else {
145                    resultingType = getProposedType();
146                    if ((predecessor != null) && predecessor.equals(DisplayType.none)) {
147                        predecessor = null;
148                    }
149                    if ((successor != null) && successor.equals(DisplayType.none)) {
150                        successor = null;
151                    }
152                    if ((predecessor != null) && predecessor.equals(DisplayType.column)) {
153                        predecessor = DisplayType.singleline;
154                    }
155                    if ((successor != null) && successor.equals(DisplayType.column)) {
156                        successor = DisplayType.singleline;
157                    }
158                    boolean strong = m_rule.equals(EvaluationRule.none)
159                        || (m_rule.equals(EvaluationRule.optional) && m_default.equals(DisplayType.singleline))
160                        || (m_rule.equals(EvaluationRule.labelLength) && m_default.equals(DisplayType.wide));
161                    if (((predecessor == null) || (successor == null)) && strong) {
162                        resultingType = m_default;
163                    } else if ((predecessor != null) || (successor != null)) {
164
165                        // check if the proposed type matches neither the type of the predecessor nor the type of the successor
166                        if (!(((predecessor != null) && resultingType.equals(predecessor))
167                            || ((successor != null) && resultingType.equals(successor)))) {
168                            DisplayType match = (predecessor != null)
169                                && (predecessor.equals(DisplayType.wide) || predecessor.equals(DisplayType.singleline))
170                                ? predecessor
171                                : ((successor != null)
172                                    && (successor.equals(DisplayType.wide) || successor.equals(DisplayType.singleline))
173                                    ? successor
174                                    : null);
175                            resultingType = match != null ? match : resultingType;
176                        }
177                    }
178                }
179            }
180            m_config.setDisplayType(resultingType.name());
181            return m_config;
182        }
183
184        /**
185         * Returns the proposed display type.<p>
186         *
187         * @return the proposed display type
188         */
189        protected DisplayType getProposedType() {
190
191            DisplayType resultingType = m_configuredType;
192            if (resultingType.equals(DisplayType.none)) {
193                switch (m_rule) {
194                    case rootLevel:
195                    case labelLength:
196                        resultingType = DisplayType.wide;
197                        break;
198                    case optional:
199                        resultingType = DisplayType.singleline;
200                        break;
201                    default:
202                        resultingType = m_default;
203
204                }
205            }
206            return resultingType;
207        }
208
209        /**
210         * Sets the attribute name.<p>
211         *
212         * @param attributeName the attribute name
213         */
214        protected void setAttributeName(String attributeName) {
215
216            m_attributeName = attributeName;
217        }
218    }
219
220    /** Widget display type evaluation rules. */
221    protected enum EvaluationRule {
222        /** Label length rule. */
223        labelLength,
224        /** No rule applied. */
225        none,
226        /** Optional field rule. */
227        optional,
228        /** Root level rule. */
229        rootLevel
230    }
231
232    /** Logger instance for this class. */
233    private static final Log LOG = CmsLog.getLog(CmsContentTypeVisitor.class);
234
235    /** The localization macro start sequence. */
236    private static final String MESSAGE_MACRO_START = ""
237        + I_CmsMacroResolver.MACRO_DELIMITER
238        + I_CmsMacroResolver.MACRO_START
239        + CmsMacroResolver.KEY_LOCALIZED_PREFIX;
240
241    /** The old style localization macro start sequence. */
242    private static final String MESSAGE_MACRO_START_OLD = ""
243        + I_CmsMacroResolver.MACRO_DELIMITER_OLD
244        + I_CmsMacroResolver.MACRO_START_OLD
245        + CmsMacroResolver.KEY_LOCALIZED_PREFIX;
246
247    /** The attribute configurations. */
248    private Map<String, CmsAttributeConfiguration> m_attributeConfigurations;
249
250    /** The CMS context used for this visitor. */
251    private CmsObject m_cms;
252
253    /** Map from attribute names to complex widget configurations. */
254    private Map<String, CmsComplexWidgetData> m_complexWidgets = new HashMap<String, CmsComplexWidgetData>();
255
256    /** The content handler. */
257    private I_CmsXmlContentHandler m_contentHandler;
258
259    /** The dynamically loaded attribute names. */
260    private List<String> m_dynamicallyLoaded;
261
262    /** The optional dynamic categoy fields. */
263    private CmsDynamicCategoryFieldList m_dynamicCategoryFields = new CmsDynamicCategoryFieldList();
264
265    /** The content resource. */
266    private CmsFile m_file;
267
268    /** Indicates the visited content has fields that are configured to be invisible to the current user. */
269    private boolean m_hasInvisible;
270
271    /** The content locale. */
272    private Locale m_locale;
273
274    /** The locale synchronized attribute names. */
275    private List<String> m_localeSynchronizations;
276
277    /** The messages. */
278    private CmsMultiMessages m_messages;
279
280    /** The registered types. */
281    private Map<String, CmsType> m_registeredTypes;
282
283    /** The root content definition. */
284    private CmsXmlContentDefinition m_rootContentDefinition;
285
286    /** The tab informations. */
287    private List<CmsTabInfo> m_tabInfos;
288
289    /** The widget configurations. */
290    private Map<String, CmsExternalWidgetConfiguration> m_widgetConfigurations;
291
292    /** The widgets encountered by this visitor. */
293    private List<I_CmsWidget> m_widgets = new ArrayList<I_CmsWidget>();
294
295    /**
296     * Constructor.<p>
297     *
298     * @param cms the CMS context
299     * @param file the content file
300     * @param locale the content locale
301     */
302    public CmsContentTypeVisitor(CmsObject cms, CmsFile file, Locale locale) {
303
304        m_file = file;
305        m_cms = cms;
306        m_locale = locale;
307    }
308
309    /**
310     * Returns the tab informations for the given content definition.<p>
311     * @param cms the CMS context
312     * @param definition the content definition
313     * @param messages the localization messages
314     *
315     * @return the tab informations
316     */
317    public static List<CmsTabInfo> collectTabInfos(
318        CmsObject cms,
319        CmsXmlContentDefinition definition,
320        CmsMessages messages) {
321
322        List<CmsTabInfo> result = new ArrayList<CmsTabInfo>();
323        CmsMacroResolver resolver = new CmsMacroResolver();
324        resolver.setCmsObject(cms);
325        resolver.setMessages(messages);
326        CmsKeyDummyMacroResolver keyResolver = new CmsKeyDummyMacroResolver(resolver);
327
328        if (definition.getContentHandler().getTabs() != null) {
329            for (CmsXmlContentTab xmlTab : definition.getContentHandler().getTabs()) {
330                String tabKey = null;
331                String tabName;
332
333                // in case the tab name attribute contains a localization macro
334                if (xmlTab.getTabName().contains(MESSAGE_MACRO_START)
335                    || xmlTab.getTabName().contains(MESSAGE_MACRO_START_OLD)) {
336                    tabName = resolver.resolveMacros(xmlTab.getTabName());
337                    tabKey = CmsKeyDummyMacroResolver.getKey(keyResolver.resolveMacros(xmlTab.getTabName()));
338                } else {
339                    tabName = messages.keyDefault(
340                        A_CmsWidget.LABEL_PREFIX + definition.getInnerName() + "." + xmlTab.getTabName(),
341                        xmlTab.getTabName());
342
343                    tabKey = A_CmsWidget.LABEL_PREFIX + definition.getInnerName() + "." + xmlTab.getTabName();
344                }
345
346                String descriptionKey = null;
347                if (xmlTab.getDescription() != null) {
348                    descriptionKey = CmsKeyDummyMacroResolver.getKey(
349                        keyResolver.resolveMacros(xmlTab.getDescription()));
350                }
351
352                result.add(
353                    new CmsTabInfo(
354                        tabName,
355                        tabKey,
356                        xmlTab.getTabName(),
357                        xmlTab.getIdName(),
358                        xmlTab.getStartName(),
359                        xmlTab.isCollapsed(),
360                        resolver.resolveMacros(xmlTab.getDescription()),
361                        descriptionKey,
362                        xmlTab.getDescription()));
363            }
364        }
365        return result;
366    }
367
368    /**
369     * Gets the CMS context.<p>
370     *
371     * @return the CMS context
372     */
373    public CmsObject getCmsObject() {
374
375        return m_cms;
376    }
377
378    /**
379     * Gets the list of widgets which have been processed by this visitor.<p>
380     *
381     * @return the list of widget
382     */
383    public List<I_CmsWidget> getCollectedWidgets() {
384
385        return Collections.unmodifiableList(m_widgets);
386    }
387
388    /**
389     * Gets the map of complex widget configurations.<p>
390     *
391     * @return a map from attribute names to complex widget configurations
392     */
393    public Map<String, CmsComplexWidgetData> getComplexWidgetData() {
394
395        return m_complexWidgets;
396    }
397
398    /**
399     * Returns the label for this value.<p>
400     *
401     * @param value the value
402     * @param defaultValue the default value
403     *
404     * @return the label
405     */
406    public String getLabel(I_CmsXmlSchemaType value, String defaultValue) {
407
408        I_CmsXmlContentHandler handler = value.getContentDefinition().getContentHandler();
409        if (handler instanceof CmsDefaultXmlContentHandler) {
410            CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler;
411            String label = defaultHandler.getFieldLabels().get(value.getName());
412            if (label != null) {
413                CmsMacroResolver resolver = new CmsMacroResolver();
414                resolver.setCmsObject(m_cms);
415                resolver.setKeepEmptyMacros(true);
416                resolver.setMessages(m_messages);
417                return resolver.resolveMacros(label);
418            }
419        }
420        StringBuffer result = new StringBuffer(64);
421        result.append(A_CmsWidget.LABEL_PREFIX);
422        result.append(getTypeKey(value));
423        return m_messages.keyDefault(result.toString(), defaultValue);
424    }
425
426    /**
427     * Gets the optional dynamic category fields collected so far.
428     *
429     * @return the optional dynamic category fields
430     */
431    public CmsDynamicCategoryFieldList getOptionalDynamicCategoryFields() {
432
433        return m_dynamicCategoryFields;
434    }
435
436    /**
437     * Returns the tabInfos.<p>
438     *
439     * @return the tabInfos
440     */
441    public List<CmsTabInfo> getTabInfos() {
442
443        return m_tabInfos;
444    }
445
446    /**
447     * Returns if the visited content has invisible fields.<p>
448     *
449     * @return <code>true</code> if the visited content has invisible fields
450     */
451    public boolean hasInvisibleFields() {
452
453        return m_hasInvisible;
454    }
455
456    /**
457     * Returns <code>true</code> if the value of the attribute is dynamically loaded.
458     * @param attributeName the attribute to check
459     * @return <code>true</code> if the value of the attribute is dynamically loaded.
460     */
461    public boolean isDynamicallyLoaded(String attributeName) {
462
463        return m_dynamicallyLoaded.contains(attributeName);
464    }
465
466    /**
467     * Checks if the content type widgets are compatible with the new content editor.<p>
468     *
469     * @param xmlContentDefinition the content definition
470     *
471     * @return <code>true</code> if the content type widgets are compatible with the new content editor
472     *
473     * @throws CmsXmlException if something goes wrong reading the type widget
474     */
475    public boolean isEditorCompatible(CmsXmlContentDefinition xmlContentDefinition) throws CmsXmlException {
476
477        boolean result = true;
478        for (I_CmsXmlSchemaType subType : xmlContentDefinition.getTypeSequence()) {
479            if (subType.isSimpleType()) {
480                result = isEditorCompatible((A_CmsXmlContentValue)subType);
481            } else {
482                CmsXmlContentDefinition subTypeDefinition = ((CmsXmlNestedContentDefinition)subType).getNestedContentDefinition();
483                result = isEditorCompatible(subTypeDefinition);
484            }
485            if (!result) {
486                break;
487            }
488        }
489        return result;
490    }
491
492    /**
493     * Visits all types within the XML content definition.<p>
494     *
495     * @param xmlContentDefinition the content definition
496     * @param messageLocale the locale
497     */
498    /**
499     * Visits all types within the XML content definition.<p>
500     *
501     * @param xmlContentDefinition the content definition
502     * @param messageLocale the locale
503     */
504    public void visitTypes(CmsXmlContentDefinition xmlContentDefinition, Locale messageLocale) {
505
506        m_rootContentDefinition = xmlContentDefinition;
507        m_contentHandler = xmlContentDefinition.getContentHandler();
508        CmsMessages messages = null;
509        m_messages = new CmsMultiMessages(messageLocale);
510        m_messages.setFallbackHandler(xmlContentDefinition.getContentHandler().getMessageKeyHandler());
511
512        try {
513            messages = OpenCms.getWorkplaceManager().getMessages(messageLocale);
514            if (messages != null) {
515                m_messages.addMessages(messages);
516            }
517            messages = m_contentHandler.getMessages(messageLocale);
518            if (messages != null) {
519                m_messages.addMessages(messages);
520            }
521        } catch (Exception e) {
522            // may happen during start up
523            LOG.debug(e.getMessage(), e);
524        }
525        // generate a new multi messages object and add the messages from the workplace
526
527        m_attributeConfigurations = new HashMap<String, CmsAttributeConfiguration>();
528        m_widgetConfigurations = new HashMap<String, CmsExternalWidgetConfiguration>();
529        m_registeredTypes = new HashMap<String, CmsType>();
530        m_localeSynchronizations = new ArrayList<String>();
531        m_dynamicallyLoaded = new ArrayList<String>();
532        m_tabInfos = collectTabInfos(m_cms, xmlContentDefinition, m_messages);
533        readTypes(xmlContentDefinition, "");
534    }
535
536    /**
537     * Returns the attribute configurations.<p>
538     *
539     * @return the attribute configurations
540     */
541    protected Map<String, CmsAttributeConfiguration> getAttributeConfigurations() {
542
543        return m_attributeConfigurations;
544    }
545
546    /**
547     * Returns the locale synchronized attribute names.<p>
548     *
549     * @return the locale synchronized attribute names
550     */
551    protected List<String> getLocaleSynchronizations() {
552
553        return m_localeSynchronizations;
554    }
555
556    /**
557     * Returns the types of the visited content definition.<p>
558     *
559     * @return the types
560     */
561    protected Map<String, CmsType> getTypes() {
562
563        return m_registeredTypes;
564    }
565
566    /**
567     * Returns the external widget configurations.<p>
568     *
569     * @return the external widget configurations
570     */
571    protected Collection<CmsExternalWidgetConfiguration> getWidgetConfigurations() {
572
573        return m_widgetConfigurations.values();
574    }
575
576    /**
577     * Returns the help information for this value.<p>
578     *
579     * @param value the value
580     *
581     * @return the help information
582     */
583    private String getHelp(I_CmsXmlSchemaType value) {
584
585        StringBuffer result = new StringBuffer(64);
586        I_CmsXmlContentHandler handler = value.getContentDefinition().getContentHandler();
587        if (handler instanceof CmsDefaultXmlContentHandler) {
588            CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler;
589            String help = defaultHandler.getFieldHelp().get(value.getName());
590            if (help != null) {
591                CmsMacroResolver resolver = new CmsMacroResolver();
592                resolver.setCmsObject(m_cms);
593                resolver.setKeepEmptyMacros(true);
594                resolver.setMessages(m_messages);
595                return resolver.resolveMacros(help);
596            }
597        }
598        result.append(A_CmsWidget.LABEL_PREFIX);
599        result.append(getTypeKey(value));
600        result.append(A_CmsWidget.HELP_POSTFIX);
601        return m_messages.keyDefault(result.toString(), null);
602    }
603
604    /**
605     * Returns the schema type message key.<p>
606     *
607     * @param value the schema type
608     *
609     * @return the schema type message key
610     */
611    private String getTypeKey(I_CmsXmlSchemaType value) {
612
613        StringBuffer result = new StringBuffer(64);
614        result.append(value.getContentDefinition().getInnerName());
615        result.append('.');
616        result.append(value.getName());
617        return result.toString();
618    }
619
620    /**
621     * Checks if the content value widget is compatible with the new content editor.<p>
622     *
623     * @param schemaType the content value type
624     *
625     * @return <code>true</code> if the content value widget is compatible with the new content editor
626     *
627     * @throws CmsXmlException if something goes wrong reading the type widget
628     */
629    private boolean isEditorCompatible(A_CmsXmlContentValue schemaType) throws CmsXmlException {
630
631        boolean result = false;
632        I_CmsXmlContentHandler contentHandler = schemaType.getContentDefinition().getContentHandler();
633        // We don't care about the old editor for the 'inheritable' widget configuration,
634        // so we're using the old getWidget method here
635        I_CmsWidget widget = contentHandler.getWidget(schemaType);
636        result = (widget == null) || (widget instanceof I_CmsADEWidget);
637        return result;
638    }
639
640    /**
641     * Returns if an element with the given path will be displayed at root level of a content editor tab.<p>
642     *
643     * @param path the element path
644     *
645     * @return <code>true</code> if an element with the given path will be displayed at root level of a content editor tab
646     */
647    private boolean isTabRootLevel(String path) {
648
649        path = path.substring(1);
650        if (!path.contains("/")) {
651            return true;
652        }
653        if (m_tabInfos != null) {
654            for (CmsTabInfo info : m_tabInfos) {
655                if (info.isCollapsed()
656                    && path.startsWith(info.getStartName())
657                    && !path.substring(info.getStartName().length() + 1).contains("/")) {
658                    return true;
659                }
660            }
661        }
662        return false;
663    }
664
665    /**
666     * Reads the attribute configuration for the given schema type. May return <code>null</code> if no special configuration was set.<p>
667     *
668     * @param schemaType the schema type
669     * @param path the attribute path
670     *
671     * @return the attribute configuration
672     */
673    private DisplayTypeEvaluator readConfiguration(A_CmsXmlContentValue schemaType, String path) {
674
675        String widgetName = null;
676        String widgetConfig = null;
677        CmsObject cms = getCmsObject();
678        String label = getLabel(schemaType, schemaType.getName());
679        // set the default display type
680        DisplayType configuredType = DisplayType.none;
681        DisplayType defaultType = DisplayType.none;
682        EvaluationRule rule = EvaluationRule.none;
683        try {
684            if ((cms.getRequestContext().getAttribute(CmsRequestContext.ATTRIBUTE_ADE_CONTEXT_PATH) == null)
685                && (m_file != null)) {
686                cms.getRequestContext().setAttribute(
687                    CmsRequestContext.ATTRIBUTE_ADE_CONTEXT_PATH,
688                    m_file.getRootPath());
689            }
690            WidgetInfo widgetInfo = CmsWidgetUtil.collectWidgetInfo(
691                cms,
692                m_rootContentDefinition,
693                path,
694                m_messages,
695                null);
696            I_CmsWidget widget = widgetInfo.getWidget();
697            I_CmsComplexWidget complexWidget = widgetInfo.getComplexWidget();
698            configuredType = widgetInfo.getDisplayType();
699            if (configuredType.equals(DisplayType.none) && schemaType.isSimpleType()) {
700                // check the type is on the root level of the document, those will be displayed 'wide'
701                // the path will always have a leading '/'
702                // also in case the label has more than 15 characters, we display 'wide'
703                if (isTabRootLevel(path)) {
704                    rule = EvaluationRule.rootLevel;
705                } else if (label.length() > 15) {
706                    rule = EvaluationRule.labelLength;
707                } else if ((schemaType.getMinOccurs() == 0)) {
708                    rule = EvaluationRule.optional;
709                }
710            }
711            if (widget != null) {
712                widgetName = widget.getClass().getName();
713                if ((configuredType == DisplayType.column)
714                    && !(schemaType.isSimpleType()
715                        && (schemaType.getMaxOccurs() == 1)
716                        && widget.isCompactViewEnabled())) {
717                    // column view is not allowed for this widget
718                    configuredType = DisplayType.singleline;
719                }
720                long timer = 0;
721                if (widget instanceof I_CmsADEWidget) {
722                    if (CmsContentService.LOG.isDebugEnabled()) {
723                        timer = System.currentTimeMillis();
724                    }
725                    I_CmsADEWidget adeWidget = (I_CmsADEWidget)widget;
726                    defaultType = adeWidget.getDefaultDisplayType();
727                    widgetName = adeWidget.getWidgetName();
728
729                    widgetConfig = adeWidget.getConfiguration(cms, schemaType, m_messages, m_file, m_locale);
730                    if (!adeWidget.isInternal() && !m_widgetConfigurations.containsKey(widgetName)) {
731                        CmsExternalWidgetConfiguration externalConfiguration = new CmsExternalWidgetConfiguration(
732                            widgetName,
733                            adeWidget.getInitCall(),
734                            adeWidget.getJavaScriptResourceLinks(cms),
735                            adeWidget.getCssResourceLinks(cms));
736                        m_widgetConfigurations.put(widgetName, externalConfiguration);
737                    }
738                    if (CmsContentService.LOG.isDebugEnabled()) {
739                        CmsContentService.LOG.debug(
740                            Messages.get().getBundle().key(
741                                Messages.LOG_TAKE_READING_WIDGET_CONFIGURATION_TIME_2,
742                                widgetName,
743                                "" + (System.currentTimeMillis() - timer)));
744                    }
745
746                }
747                m_widgets.add(widget);
748            }
749            if (complexWidget != null) {
750                CmsComplexWidgetData widgetData = complexWidget.getWidgetData(m_cms);
751                CmsExternalWidgetConfiguration externalConfig = widgetData.getExternalWidgetConfiguration();
752                if (externalConfig != null) {
753                    m_widgetConfigurations.put(complexWidget.getName(), externalConfig);
754                }
755                m_complexWidgets.put(CmsContentService.getAttributeName(schemaType), widgetData);
756            }
757        } catch (Exception e) {
758            LOG.error(e.getLocalizedMessage(), e);
759        }
760
761        // remove the leading slash from element path to check visibility
762        boolean visible = !m_contentHandler.hasVisibilityHandlers()
763            || m_contentHandler.isVisible(cms, schemaType, path.substring(1), m_file, m_locale);
764        if (!visible) {
765            // set the has invisible flag
766            m_hasInvisible = true;
767        }
768        boolean localeSynchronized = (m_contentHandler.hasSynchronizedElements()
769            && m_contentHandler.getSynchronizations(true).getSynchronizationPaths().contains(path.substring(1)))
770            || schemaType.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME);
771
772        boolean dynamicallyLoaded = (schemaType instanceof CmsXmlDynamicCategoryValue)
773            || (schemaType instanceof CmsXmlAccessRestrictionValue);
774
775        CmsAttributeConfiguration result = new CmsAttributeConfiguration(
776            label,
777            getHelp(schemaType),
778            widgetName,
779            widgetConfig,
780            readDefaultValue(schemaType, path),
781            configuredType.name(),
782            visible,
783            localeSynchronized,
784            dynamicallyLoaded);
785        return new DisplayTypeEvaluator(result, configuredType, defaultType, rule);
786    }
787
788    /**
789     * Reads the default value for the given type.<p>
790     *
791     * @param schemaType the schema type
792     * @param path the element path
793     *
794     * @return the default value
795     */
796    private String readDefaultValue(I_CmsXmlSchemaType schemaType, String path) {
797
798        return m_contentHandler.getDefault(getCmsObject(), m_file, schemaType, path, m_locale);
799    }
800
801    /**
802     * Reads the types from the given content definition and adds the to the map of already registered
803     * types if necessary.<p>
804     *
805     * @param xmlContentDefinition the XML content definition
806     * @param path the element path
807     *
808     * @return the type
809     */
810    private CmsType readTypes(CmsXmlContentDefinition xmlContentDefinition, String path) {
811
812        String typeName = (CmsStringUtil.isEmptyOrWhitespaceOnly(path) ? "" : (path + ":"))
813            + CmsContentService.getTypeUri(xmlContentDefinition);
814        if (m_registeredTypes.containsKey(typeName)) {
815            return m_registeredTypes.get(typeName);
816        }
817        CmsType type = new CmsType(typeName);
818        type.setChoiceMaxOccurrence(xmlContentDefinition.getChoiceMaxOccurs());
819        m_registeredTypes.put(typeName, type);
820        CmsType choiceType = null;
821        if (type.isChoice()) {
822            choiceType = new CmsType(typeName + "/" + CmsType.CHOICE_ATTRIBUTE_NAME);
823            m_registeredTypes.put(choiceType.getId(), choiceType);
824            type.addAttribute(CmsType.CHOICE_ATTRIBUTE_NAME, choiceType, 1, xmlContentDefinition.getChoiceMaxOccurs());
825        }
826        ArrayList<DisplayTypeEvaluator> evaluators = new ArrayList<DisplayTypeEvaluator>();
827        for (I_CmsXmlSchemaType subType : xmlContentDefinition.getTypeSequence()) {
828            String subTypeName = null;
829            String childPath = path + "/" + subType.getName();
830            String subAttributeName = CmsContentService.getAttributeName(subType.getName(), typeName);
831            DisplayTypeEvaluator ev = readConfiguration((A_CmsXmlContentValue)subType, childPath);
832            ev.setAttributeName(subAttributeName);
833            evaluators.add(ev);
834            CmsType subEntityType;
835            if (subType.isSimpleType()) {
836                subTypeName = CmsContentService.TYPE_NAME_PREFIX + subType.getTypeName();
837                if (!m_registeredTypes.containsKey(subTypeName)) {
838                    subEntityType = new CmsType(subTypeName);
839                    m_registeredTypes.put(subTypeName, subEntityType);
840                } else {
841                    subEntityType = m_registeredTypes.get(subTypeName);
842                }
843            } else {
844                CmsXmlContentDefinition subTypeDefinition = ((CmsXmlNestedContentDefinition)subType).getNestedContentDefinition();
845                subTypeName = CmsContentService.getTypeUri(subTypeDefinition);
846                subEntityType = readTypes(subTypeDefinition, childPath);
847            }
848            if (choiceType != null) {
849                choiceType.addAttribute(
850                    subAttributeName,
851                    subEntityType,
852                    subType.getMinOccurs(),
853                    subType.getMaxOccurs());
854            } else {
855                int minOccurs = subType.getMinOccurs();
856                if ((subType instanceof CmsXmlDynamicCategoryValue) && (subType.getMinOccurs() == 0)) {
857                    String dynamicCategoryPath;
858                    if ("".equals(path)) {
859                        dynamicCategoryPath = subType.getName();
860                    } else {
861                        dynamicCategoryPath = path + "/" + subType.getName();
862                    }
863                    m_dynamicCategoryFields.add(dynamicCategoryPath);
864                    minOccurs = 1;
865                }
866                type.addAttribute(subAttributeName, subEntityType, minOccurs, subType.getMaxOccurs());
867            }
868        }
869        DisplayType predecessor = null;
870        for (int i = 0; i < evaluators.size(); i++) {
871            DisplayTypeEvaluator ev = evaluators.get(i);
872            DisplayType successor = ((i + 1) < evaluators.size()) ? evaluators.get(i + 1).getProposedType() : null;
873            CmsAttributeConfiguration evaluated = ev.getEvaluatedConfiguration(predecessor, successor);
874            m_attributeConfigurations.put(ev.getAttributeName(), evaluated);
875            if (evaluated.isLocaleSynchronized()) {
876                m_localeSynchronizations.add(ev.getAttributeName());
877            }
878            if (evaluated.isDynamicallyLoaded()) {
879                m_dynamicallyLoaded.add(ev.getAttributeName());
880            }
881            predecessor = DisplayType.valueOf(evaluated.getDisplayType());
882        }
883        return type;
884    }
885}