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                    tabKey = A_CmsWidget.LABEL_PREFIX + definition.getInnerName() + "." + xmlTab.getTabName();
343                }
344
345                String descriptionKey = null;
346                if (xmlTab.getDescription() != null) {
347                    descriptionKey = CmsKeyDummyMacroResolver.getKey(
348                        keyResolver.resolveMacros(xmlTab.getDescription()));
349                }
350
351                result.add(
352                    new CmsTabInfo(
353                        tabName,
354                        tabKey,
355                        xmlTab.getIdName(),
356                        xmlTab.getStartName(),
357                        xmlTab.isCollapsed(),
358                        resolver.resolveMacros(xmlTab.getDescription()),
359                        descriptionKey));
360            }
361        }
362        return result;
363    }
364
365    /**
366     * Gets the CMS context.<p>
367     *
368     * @return the CMS context
369     */
370    public CmsObject getCmsObject() {
371
372        return m_cms;
373    }
374
375    /**
376     * Gets the list of widgets which have been processed by this visitor.<p>
377     *
378     * @return the list of widget
379     */
380    public List<I_CmsWidget> getCollectedWidgets() {
381
382        return Collections.unmodifiableList(m_widgets);
383    }
384
385    /**
386     * Gets the map of complex widget configurations.<p>
387     *
388     * @return a map from attribute names to complex widget configurations
389     */
390    public Map<String, CmsComplexWidgetData> getComplexWidgetData() {
391
392        return m_complexWidgets;
393    }
394
395    /**
396     * Returns the label for this value.<p>
397     *
398     * @param value the value
399     * @param defaultValue the default value
400     *
401     * @return the label
402     */
403    public String getLabel(I_CmsXmlSchemaType value, String defaultValue) {
404
405        I_CmsXmlContentHandler handler = value.getContentDefinition().getContentHandler();
406        if (handler instanceof CmsDefaultXmlContentHandler) {
407            CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler;
408            String label = defaultHandler.getFieldLabels().get(value.getName());
409            if (label != null) {
410                CmsMacroResolver resolver = new CmsMacroResolver();
411                resolver.setCmsObject(m_cms);
412                resolver.setKeepEmptyMacros(true);
413                resolver.setMessages(m_messages);
414                return resolver.resolveMacros(label);
415            }
416        }
417        StringBuffer result = new StringBuffer(64);
418        result.append(A_CmsWidget.LABEL_PREFIX);
419        result.append(getTypeKey(value));
420        return m_messages.keyDefault(result.toString(), defaultValue);
421    }
422
423    /**
424     * Gets the optional dynamic category fields collected so far.
425     *
426     * @return the optional dynamic category fields
427     */
428    public CmsDynamicCategoryFieldList getOptionalDynamicCategoryFields() {
429
430        return m_dynamicCategoryFields;
431    }
432
433    /**
434     * Returns the tabInfos.<p>
435     *
436     * @return the tabInfos
437     */
438    public List<CmsTabInfo> getTabInfos() {
439
440        return m_tabInfos;
441    }
442
443    /**
444     * Returns if the visited content has invisible fields.<p>
445     *
446     * @return <code>true</code> if the visited content has invisible fields
447     */
448    public boolean hasInvisibleFields() {
449
450        return m_hasInvisible;
451    }
452
453    /**
454     * Returns <code>true</code> if the value of the attribute is dynamically loaded.
455     * @param attributeName the attribute to check
456     * @return <code>true</code> if the value of the attribute is dynamically loaded.
457     */
458    public boolean isDynamicallyLoaded(String attributeName) {
459
460        return m_dynamicallyLoaded.contains(attributeName);
461    }
462
463    /**
464     * Checks if the content type widgets are compatible with the new content editor.<p>
465     *
466     * @param xmlContentDefinition the content definition
467     *
468     * @return <code>true</code> if the content type widgets are compatible with the new content editor
469     *
470     * @throws CmsXmlException if something goes wrong reading the type widget
471     */
472    public boolean isEditorCompatible(CmsXmlContentDefinition xmlContentDefinition) throws CmsXmlException {
473
474        boolean result = true;
475        for (I_CmsXmlSchemaType subType : xmlContentDefinition.getTypeSequence()) {
476            if (subType.isSimpleType()) {
477                result = isEditorCompatible((A_CmsXmlContentValue)subType);
478            } else {
479                CmsXmlContentDefinition subTypeDefinition = ((CmsXmlNestedContentDefinition)subType).getNestedContentDefinition();
480                result = isEditorCompatible(subTypeDefinition);
481            }
482            if (!result) {
483                break;
484            }
485        }
486        return result;
487    }
488
489    /**
490     * Visits all types within the XML content definition.<p>
491     *
492     * @param xmlContentDefinition the content definition
493     * @param messageLocale the locale
494     */
495    /**
496     * Visits all types within the XML content definition.<p>
497     *
498     * @param xmlContentDefinition the content definition
499     * @param messageLocale the locale
500     */
501    public void visitTypes(CmsXmlContentDefinition xmlContentDefinition, Locale messageLocale) {
502
503        m_rootContentDefinition = xmlContentDefinition;
504        m_contentHandler = xmlContentDefinition.getContentHandler();
505        CmsMessages messages = null;
506        m_messages = new CmsMultiMessages(messageLocale);
507        m_messages.setFallbackHandler(xmlContentDefinition.getContentHandler().getMessageKeyHandler());
508
509        try {
510            messages = OpenCms.getWorkplaceManager().getMessages(messageLocale);
511            if (messages != null) {
512                m_messages.addMessages(messages);
513            }
514            messages = m_contentHandler.getMessages(messageLocale);
515            if (messages != null) {
516                m_messages.addMessages(messages);
517            }
518        } catch (Exception e) {
519            // may happen during start up
520            LOG.debug(e.getMessage(), e);
521        }
522        // generate a new multi messages object and add the messages from the workplace
523
524        m_attributeConfigurations = new HashMap<String, CmsAttributeConfiguration>();
525        m_widgetConfigurations = new HashMap<String, CmsExternalWidgetConfiguration>();
526        m_registeredTypes = new HashMap<String, CmsType>();
527        m_localeSynchronizations = new ArrayList<String>();
528        m_dynamicallyLoaded = new ArrayList<String>();
529        m_tabInfos = collectTabInfos(m_cms, xmlContentDefinition, m_messages);
530        readTypes(xmlContentDefinition, "");
531    }
532
533    /**
534     * Returns the attribute configurations.<p>
535     *
536     * @return the attribute configurations
537     */
538    protected Map<String, CmsAttributeConfiguration> getAttributeConfigurations() {
539
540        return m_attributeConfigurations;
541    }
542
543    /**
544     * Returns the locale synchronized attribute names.<p>
545     *
546     * @return the locale synchronized attribute names
547     */
548    protected List<String> getLocaleSynchronizations() {
549
550        return m_localeSynchronizations;
551    }
552
553    /**
554     * Returns the types of the visited content definition.<p>
555     *
556     * @return the types
557     */
558    protected Map<String, CmsType> getTypes() {
559
560        return m_registeredTypes;
561    }
562
563    /**
564     * Returns the external widget configurations.<p>
565     *
566     * @return the external widget configurations
567     */
568    protected Collection<CmsExternalWidgetConfiguration> getWidgetConfigurations() {
569
570        return m_widgetConfigurations.values();
571    }
572
573    /**
574     * Returns the help information for this value.<p>
575     *
576     * @param value the value
577     *
578     * @return the help information
579     */
580    private String getHelp(I_CmsXmlSchemaType value) {
581
582        StringBuffer result = new StringBuffer(64);
583        I_CmsXmlContentHandler handler = value.getContentDefinition().getContentHandler();
584        if (handler instanceof CmsDefaultXmlContentHandler) {
585            CmsDefaultXmlContentHandler defaultHandler = (CmsDefaultXmlContentHandler)handler;
586            String help = defaultHandler.getFieldHelp().get(value.getName());
587            if (help != null) {
588                CmsMacroResolver resolver = new CmsMacroResolver();
589                resolver.setCmsObject(m_cms);
590                resolver.setKeepEmptyMacros(true);
591                resolver.setMessages(m_messages);
592                return resolver.resolveMacros(help);
593            }
594        }
595        result.append(A_CmsWidget.LABEL_PREFIX);
596        result.append(getTypeKey(value));
597        result.append(A_CmsWidget.HELP_POSTFIX);
598        return m_messages.keyDefault(result.toString(), null);
599    }
600
601    /**
602     * Returns the schema type message key.<p>
603     *
604     * @param value the schema type
605     *
606     * @return the schema type message key
607     */
608    private String getTypeKey(I_CmsXmlSchemaType value) {
609
610        StringBuffer result = new StringBuffer(64);
611        result.append(value.getContentDefinition().getInnerName());
612        result.append('.');
613        result.append(value.getName());
614        return result.toString();
615    }
616
617    /**
618     * Checks if the content value widget is compatible with the new content editor.<p>
619     *
620     * @param schemaType the content value type
621     *
622     * @return <code>true</code> if the content value widget is compatible with the new content editor
623     *
624     * @throws CmsXmlException if something goes wrong reading the type widget
625     */
626    private boolean isEditorCompatible(A_CmsXmlContentValue schemaType) throws CmsXmlException {
627
628        boolean result = false;
629        I_CmsXmlContentHandler contentHandler = schemaType.getContentDefinition().getContentHandler();
630        // We don't care about the old editor for the 'inheritable' widget configuration,
631        // so we're using the old getWidget method here
632        I_CmsWidget widget = contentHandler.getWidget(schemaType);
633        result = (widget == null) || (widget instanceof I_CmsADEWidget);
634        return result;
635    }
636
637    /**
638     * Returns if an element with the given path will be displayed at root level of a content editor tab.<p>
639     *
640     * @param path the element path
641     *
642     * @return <code>true</code> if an element with the given path will be displayed at root level of a content editor tab
643     */
644    private boolean isTabRootLevel(String path) {
645
646        path = path.substring(1);
647        if (!path.contains("/")) {
648            return true;
649        }
650        if (m_tabInfos != null) {
651            for (CmsTabInfo info : m_tabInfos) {
652                if (info.isCollapsed()
653                    && path.startsWith(info.getStartName())
654                    && !path.substring(info.getStartName().length() + 1).contains("/")) {
655                    return true;
656                }
657            }
658        }
659        return false;
660    }
661
662    /**
663     * Reads the attribute configuration for the given schema type. May return <code>null</code> if no special configuration was set.<p>
664     *
665     * @param schemaType the schema type
666     * @param path the attribute path
667     *
668     * @return the attribute configuration
669     */
670    private DisplayTypeEvaluator readConfiguration(A_CmsXmlContentValue schemaType, String path) {
671
672        String widgetName = null;
673        String widgetConfig = null;
674        CmsObject cms = getCmsObject();
675        String label = getLabel(schemaType, schemaType.getName());
676        // set the default display type
677        DisplayType configuredType = DisplayType.none;
678        DisplayType defaultType = DisplayType.none;
679        EvaluationRule rule = EvaluationRule.none;
680        try {
681            if ((cms.getRequestContext().getAttribute(CmsRequestContext.ATTRIBUTE_ADE_CONTEXT_PATH) == null)
682                && (m_file != null)) {
683                cms.getRequestContext().setAttribute(
684                    CmsRequestContext.ATTRIBUTE_ADE_CONTEXT_PATH,
685                    m_file.getRootPath());
686            }
687            WidgetInfo widgetInfo = CmsWidgetUtil.collectWidgetInfo(cms, m_rootContentDefinition, path, m_messages);
688            I_CmsWidget widget = widgetInfo.getWidget();
689            I_CmsComplexWidget complexWidget = widgetInfo.getComplexWidget();
690            configuredType = widgetInfo.getDisplayType();
691            if (configuredType.equals(DisplayType.none) && schemaType.isSimpleType()) {
692                // check the type is on the root level of the document, those will be displayed 'wide'
693                // the path will always have a leading '/'
694                // also in case the label has more than 15 characters, we display 'wide'
695                if (isTabRootLevel(path)) {
696                    rule = EvaluationRule.rootLevel;
697                } else if (label.length() > 15) {
698                    rule = EvaluationRule.labelLength;
699                } else if ((schemaType.getMinOccurs() == 0)) {
700                    rule = EvaluationRule.optional;
701                }
702            }
703            if (widget != null) {
704                widgetName = widget.getClass().getName();
705                if ((configuredType == DisplayType.column)
706                    && !(schemaType.isSimpleType()
707                        && (schemaType.getMaxOccurs() == 1)
708                        && widget.isCompactViewEnabled())) {
709                    // column view is not allowed for this widget
710                    configuredType = DisplayType.singleline;
711                }
712                long timer = 0;
713                if (widget instanceof I_CmsADEWidget) {
714                    if (CmsContentService.LOG.isDebugEnabled()) {
715                        timer = System.currentTimeMillis();
716                    }
717                    I_CmsADEWidget adeWidget = (I_CmsADEWidget)widget;
718                    defaultType = adeWidget.getDefaultDisplayType();
719                    widgetName = adeWidget.getWidgetName();
720
721                    widgetConfig = adeWidget.getConfiguration(cms, schemaType, m_messages, m_file, m_locale);
722                    if (!adeWidget.isInternal() && !m_widgetConfigurations.containsKey(widgetName)) {
723                        CmsExternalWidgetConfiguration externalConfiguration = new CmsExternalWidgetConfiguration(
724                            widgetName,
725                            adeWidget.getInitCall(),
726                            adeWidget.getJavaScriptResourceLinks(cms),
727                            adeWidget.getCssResourceLinks(cms));
728                        m_widgetConfigurations.put(widgetName, externalConfiguration);
729                    }
730                    if (CmsContentService.LOG.isDebugEnabled()) {
731                        CmsContentService.LOG.debug(
732                            Messages.get().getBundle().key(
733                                Messages.LOG_TAKE_READING_WIDGET_CONFIGURATION_TIME_2,
734                                widgetName,
735                                "" + (System.currentTimeMillis() - timer)));
736                    }
737
738                }
739                m_widgets.add(widget);
740            }
741            if (complexWidget != null) {
742                CmsComplexWidgetData widgetData = complexWidget.getWidgetData(m_cms);
743                CmsExternalWidgetConfiguration externalConfig = widgetData.getExternalWidgetConfiguration();
744                if (externalConfig != null) {
745                    m_widgetConfigurations.put(complexWidget.getName(), externalConfig);
746                }
747                m_complexWidgets.put(CmsContentService.getAttributeName(schemaType), widgetData);
748            }
749        } catch (Exception e) {
750            LOG.error(e.getLocalizedMessage(), e);
751        }
752
753        // remove the leading slash from element path to check visibility
754        boolean visible = !m_contentHandler.hasVisibilityHandlers()
755            || m_contentHandler.isVisible(cms, schemaType, path.substring(1), m_file, m_locale);
756        if (!visible) {
757            // set the has invisible flag
758            m_hasInvisible = true;
759        }
760        boolean localeSynchronized = (m_contentHandler.hasSynchronizedElements()
761            && m_contentHandler.getSynchronizations(true).getSynchronizationPaths().contains(path.substring(1)))
762            || schemaType.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME);
763
764        boolean dynamicallyLoaded = (schemaType instanceof CmsXmlDynamicCategoryValue)
765            || (schemaType instanceof CmsXmlAccessRestrictionValue);
766
767        CmsAttributeConfiguration result = new CmsAttributeConfiguration(
768            label,
769            getHelp(schemaType),
770            widgetName,
771            widgetConfig,
772            readDefaultValue(schemaType, path),
773            configuredType.name(),
774            visible,
775            localeSynchronized,
776            dynamicallyLoaded);
777        return new DisplayTypeEvaluator(result, configuredType, defaultType, rule);
778    }
779
780    /**
781     * Reads the default value for the given type.<p>
782     *
783     * @param schemaType the schema type
784     * @param path the element path
785     *
786     * @return the default value
787     */
788    private String readDefaultValue(I_CmsXmlSchemaType schemaType, String path) {
789
790        return m_contentHandler.getDefault(getCmsObject(), m_file, schemaType, path, m_locale);
791    }
792
793    /**
794     * Reads the types from the given content definition and adds the to the map of already registered
795     * types if necessary.<p>
796     *
797     * @param xmlContentDefinition the XML content definition
798     * @param path the element path
799     *
800     * @return the type
801     */
802    private CmsType readTypes(CmsXmlContentDefinition xmlContentDefinition, String path) {
803
804        String typeName = (CmsStringUtil.isEmptyOrWhitespaceOnly(path) ? "" : (path + ":"))
805            + CmsContentService.getTypeUri(xmlContentDefinition);
806        if (m_registeredTypes.containsKey(typeName)) {
807            return m_registeredTypes.get(typeName);
808        }
809        CmsType type = new CmsType(typeName);
810        type.setChoiceMaxOccurrence(xmlContentDefinition.getChoiceMaxOccurs());
811        m_registeredTypes.put(typeName, type);
812        CmsType choiceType = null;
813        if (type.isChoice()) {
814            choiceType = new CmsType(typeName + "/" + CmsType.CHOICE_ATTRIBUTE_NAME);
815            m_registeredTypes.put(choiceType.getId(), choiceType);
816            type.addAttribute(CmsType.CHOICE_ATTRIBUTE_NAME, choiceType, 1, xmlContentDefinition.getChoiceMaxOccurs());
817        }
818        ArrayList<DisplayTypeEvaluator> evaluators = new ArrayList<DisplayTypeEvaluator>();
819        for (I_CmsXmlSchemaType subType : xmlContentDefinition.getTypeSequence()) {
820            String subTypeName = null;
821            String childPath = path + "/" + subType.getName();
822            String subAttributeName = CmsContentService.getAttributeName(subType.getName(), typeName);
823            DisplayTypeEvaluator ev = readConfiguration((A_CmsXmlContentValue)subType, childPath);
824            ev.setAttributeName(subAttributeName);
825            evaluators.add(ev);
826            CmsType subEntityType;
827            if (subType.isSimpleType()) {
828                subTypeName = CmsContentService.TYPE_NAME_PREFIX + subType.getTypeName();
829                if (!m_registeredTypes.containsKey(subTypeName)) {
830                    subEntityType = new CmsType(subTypeName);
831                    m_registeredTypes.put(subTypeName, subEntityType);
832                } else {
833                    subEntityType = m_registeredTypes.get(subTypeName);
834                }
835            } else {
836                CmsXmlContentDefinition subTypeDefinition = ((CmsXmlNestedContentDefinition)subType).getNestedContentDefinition();
837                subTypeName = CmsContentService.getTypeUri(subTypeDefinition);
838                subEntityType = readTypes(subTypeDefinition, childPath);
839            }
840            if (choiceType != null) {
841                choiceType.addAttribute(
842                    subAttributeName,
843                    subEntityType,
844                    subType.getMinOccurs(),
845                    subType.getMaxOccurs());
846            } else {
847                int minOccurs = subType.getMinOccurs();
848                if ((subType instanceof CmsXmlDynamicCategoryValue) && (subType.getMinOccurs() == 0)) {
849                    String dynamicCategoryPath;
850                    if ("".equals(path)) {
851                        dynamicCategoryPath = subType.getName();
852                    } else {
853                        dynamicCategoryPath = path + "/" + subType.getName();
854                    }
855                    m_dynamicCategoryFields.add(dynamicCategoryPath);
856                    minOccurs = 1;
857                }
858                type.addAttribute(subAttributeName, subEntityType, minOccurs, subType.getMaxOccurs());
859            }
860        }
861        DisplayType predecessor = null;
862        for (int i = 0; i < evaluators.size(); i++) {
863            DisplayTypeEvaluator ev = evaluators.get(i);
864            DisplayType successor = ((i + 1) < evaluators.size()) ? evaluators.get(i + 1).getProposedType() : null;
865            CmsAttributeConfiguration evaluated = ev.getEvaluatedConfiguration(predecessor, successor);
866            m_attributeConfigurations.put(ev.getAttributeName(), evaluated);
867            if (evaluated.isLocaleSynchronized()) {
868                m_localeSynchronizations.add(ev.getAttributeName());
869            }
870            if (evaluated.isDynamicallyLoaded()) {
871                m_dynamicallyLoaded.add(ev.getAttributeName());
872            }
873            predecessor = DisplayType.valueOf(evaluated.getDisplayType());
874        }
875        return type;
876    }
877}