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