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