001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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 GmbH & Co. KG, 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.xml.content;
029
030import org.opencms.ade.configuration.CmsConfigurationReader;
031import org.opencms.ade.contenteditor.CmsWidgetUtil;
032import org.opencms.configuration.CmsConfigurationManager;
033import org.opencms.configuration.CmsParameterConfiguration;
034import org.opencms.db.log.CmsLogEntry;
035import org.opencms.file.CmsDataAccessException;
036import org.opencms.file.CmsFile;
037import org.opencms.file.CmsGroup;
038import org.opencms.file.CmsObject;
039import org.opencms.file.CmsProperty;
040import org.opencms.file.CmsPropertyDefinition;
041import org.opencms.file.CmsResource;
042import org.opencms.file.CmsResourceFilter;
043import org.opencms.file.CmsUser;
044import org.opencms.file.CmsVfsResourceNotFoundException;
045import org.opencms.i18n.CmsEncoder;
046import org.opencms.i18n.CmsListResourceBundle;
047import org.opencms.i18n.CmsLocaleManager;
048import org.opencms.i18n.CmsMessageContainer;
049import org.opencms.i18n.CmsMessages;
050import org.opencms.i18n.CmsMultiMessages;
051import org.opencms.i18n.CmsMultiMessages.I_KeyFallbackHandler;
052import org.opencms.i18n.CmsResourceBundleLoader;
053import org.opencms.lock.CmsLock;
054import org.opencms.main.CmsException;
055import org.opencms.main.CmsLog;
056import org.opencms.main.CmsRuntimeException;
057import org.opencms.main.CmsStaticResourceHandler;
058import org.opencms.main.OpenCms;
059import org.opencms.relations.CmsCategory;
060import org.opencms.relations.CmsCategoryService;
061import org.opencms.relations.CmsLink;
062import org.opencms.relations.CmsRelationType;
063import org.opencms.search.fields.CmsGeoCoordinateFieldMapping;
064import org.opencms.search.fields.CmsSearchField;
065import org.opencms.search.fields.CmsSearchFieldMapping;
066import org.opencms.search.fields.CmsSearchFieldMappingType;
067import org.opencms.search.fields.I_CmsSearchFieldMapping;
068import org.opencms.search.galleries.CmsGalleryNameMacroResolver;
069import org.opencms.search.solr.CmsSolrField;
070import org.opencms.security.CmsAccessControlEntry;
071import org.opencms.security.CmsPrincipal;
072import org.opencms.security.CmsRole;
073import org.opencms.security.I_CmsPrincipal;
074import org.opencms.site.CmsSite;
075import org.opencms.util.CmsDefaultSet;
076import org.opencms.util.CmsFileUtil;
077import org.opencms.util.CmsHtmlConverter;
078import org.opencms.util.CmsMacroResolver;
079import org.opencms.util.CmsStringUtil;
080import org.opencms.util.CmsUUID;
081import org.opencms.util.I_CmsMacroResolver;
082import org.opencms.widgets.CmsCategoryWidget;
083import org.opencms.widgets.CmsDisplayWidget;
084import org.opencms.widgets.I_CmsComplexWidget;
085import org.opencms.widgets.I_CmsWidget;
086import org.opencms.workplace.CmsWorkplace;
087import org.opencms.workplace.editors.CmsXmlContentWidgetVisitor;
088import org.opencms.workplace.editors.directedit.I_CmsEditHandler;
089import org.opencms.xml.CmsXmlContentDefinition;
090import org.opencms.xml.CmsXmlEntityResolver;
091import org.opencms.xml.CmsXmlException;
092import org.opencms.xml.CmsXmlGenericWrapper;
093import org.opencms.xml.CmsXmlUtils;
094import org.opencms.xml.containerpage.CmsFormatterBean;
095import org.opencms.xml.containerpage.CmsFormatterConfiguration;
096import org.opencms.xml.containerpage.CmsSchemaFormatterBeanWrapper;
097import org.opencms.xml.containerpage.I_CmsFormatterBean;
098import org.opencms.xml.content.CmsGeoMappingConfiguration.Entry;
099import org.opencms.xml.content.CmsGeoMappingConfiguration.EntryType;
100import org.opencms.xml.content.CmsMappingResolutionContext.AttributeType;
101import org.opencms.xml.types.CmsXmlCategoryValue;
102import org.opencms.xml.types.CmsXmlDisplayFormatterValue;
103import org.opencms.xml.types.CmsXmlDynamicCategoryValue;
104import org.opencms.xml.types.CmsXmlNestedContentDefinition;
105import org.opencms.xml.types.CmsXmlStringValue;
106import org.opencms.xml.types.CmsXmlVarLinkValue;
107import org.opencms.xml.types.CmsXmlVfsFileValue;
108import org.opencms.xml.types.I_CmsXmlContentValue;
109import org.opencms.xml.types.I_CmsXmlContentValue.SearchContentType;
110import org.opencms.xml.types.I_CmsXmlSchemaType;
111import org.opencms.xml.types.I_CmsXmlValidateWithMessage;
112
113import java.io.IOException;
114import java.io.InputStream;
115import java.util.ArrayList;
116import java.util.Arrays;
117import java.util.Collections;
118import java.util.HashMap;
119import java.util.HashSet;
120import java.util.Iterator;
121import java.util.LinkedHashMap;
122import java.util.LinkedHashSet;
123import java.util.List;
124import java.util.Locale;
125import java.util.Map;
126import java.util.Set;
127import java.util.TreeSet;
128import java.util.regex.Pattern;
129import java.util.regex.PatternSyntaxException;
130
131import javax.servlet.ServletRequest;
132
133import org.apache.commons.logging.Log;
134
135import org.antlr.stringtemplate.StringTemplate;
136import org.antlr.stringtemplate.StringTemplateGroup;
137import org.dom4j.Document;
138import org.dom4j.DocumentException;
139import org.dom4j.DocumentHelper;
140import org.dom4j.Element;
141import org.dom4j.Node;
142
143import com.google.common.base.Optional;
144import com.google.common.collect.ArrayListMultimap;
145import com.google.common.collect.Lists;
146import com.google.common.collect.Maps;
147import com.google.common.collect.Multimap;
148
149/**
150 * Default implementation for the XML content handler, will be used by all XML contents that do not
151 * provide their own handler.<p>
152 *
153 * @since 6.0.0
154 */
155public class CmsDefaultXmlContentHandler implements I_CmsXmlContentHandler, I_CmsXmlContentVisibilityHandler {
156
157    /**
158     * Enum for IfInvalidRelation field setting values.
159     */
160    public enum InvalidRelationAction {
161        /** Remove the field's parent. */
162        removeParent,
163
164        /** Only remove the field itself. */
165        removeSelf
166    }
167
168    /**
169     * Contains the visibility handler configuration for a content field path.<p>
170     */
171    protected static class VisibilityConfiguration {
172
173        /** The handler instance. */
174        private I_CmsXmlContentVisibilityHandler m_handler;
175
176        /** The handler configuration parameters. */
177        private String m_params;
178
179        /**
180         * Constructor.<p>
181         *
182         * @param handler the handler instance
183         * @param params the handler configuration parameteres
184         */
185        protected VisibilityConfiguration(I_CmsXmlContentVisibilityHandler handler, String params) {
186
187            m_handler = handler;
188            m_params = params;
189        }
190
191        /**
192         * Returns the visibility handler instance.<p>
193         *
194         * @return the handler instance
195         */
196        public I_CmsXmlContentVisibilityHandler getHandler() {
197
198            return m_handler;
199        }
200
201        /**
202         * Returns the visibility handler configuration parameters.<p>
203         *
204         * @return the configuration parameters
205         */
206        public String getParams() {
207
208            return m_params;
209        }
210    }
211
212    /** Enum for field setting element names which are not already defined elsewhere. */
213    enum FieldSettingElems {
214        /** Element name. */
215        Class,
216
217        /** Element name. */
218        DefaultResolveMacros,
219
220        /** Element name. */
221        Display,
222
223        /** Element name. */
224        FieldVisibility,
225
226        /** Element name. */
227        IfInvalidRelation,
228
229        /** Element name. */
230        Invalidate,
231
232        /** Element name. */
233        Mapping,
234
235        /** Element name. */
236        MapTo,
237
238        /** Element name. */
239        NestedFormatter,
240
241        /** Element name. */
242        Params,
243
244        /** Element name. */
245        Relation,
246
247        /** Element name. */
248        Search,
249
250        /** Element name. */
251        Synchronization,
252
253        /** Element name. */
254        Type,
255
256        /** Element name. */
257        UseDefault,
258
259        /** Element name. */
260        Visibility
261    }
262
263    /**
264     * Callback interface for methods that take an XML element and throw CmsXmlException.<p>
265     */
266    interface I_Callback {
267
268        /**
269         * Callback method.<p>
270         *
271         * @param elem the parameter element
272         * @throws CmsXmlException for XML errors
273         */
274        void accept(Element elem) throws CmsXmlException;
275    }
276
277    /**
278     * Bean for holding information about a single mapping.
279     */
280    private class MappingInfo {
281
282        /** The mapping source. */
283        private String m_source;
284
285        /** The mapping target. */
286        private String m_target;
287
288        /**
289         * Creates a new instance.
290         *
291         * @param source the mapping source
292         * @param target the mapping target
293         */
294        public MappingInfo(String source, String target) {
295
296            super();
297            m_source = source;
298            m_target = target;
299        }
300
301        /**
302         * Checks if the mapping can be used for reverse mapping of availability data.
303         *
304         * @return true if the mapping can be used for reverse availability mapping
305         */
306        public boolean canBeUsedForReverseAvailabilityMapping() {
307
308            return exists() && !isMappingUsingDefault(m_source, m_target) && checkIndexesNotSetOrOne(m_source);
309        }
310
311        /**
312         * Checks if the mapping actually exists.
313         *
314         * @return true if the mapping exists
315         */
316        public boolean exists() {
317
318            return (m_source != null) && (m_target != null);
319        }
320
321        /**
322         * Gets the mapping source.
323         *
324         * @return the mapping source
325         */
326        public String getSource() {
327
328            return m_source;
329        }
330
331        /**
332         * Gets the mapping target.
333         *
334         * @return the mapping target
335         */
336        public String getTarget() {
337
338            return m_target;
339        }
340
341        /**
342         * Checks that the components of an xpath have no indexes or index [1].
343         *
344         * @param xpath the xpath to check
345         *
346         * @return true if all indexes are either [1] or not set
347         */
348        private boolean checkIndexesNotSetOrOne(String xpath) {
349
350            return CmsXmlUtils.splitXpath(xpath).stream().allMatch(
351                component -> indexesNotSetOrOne.contains(CmsXmlUtils.getXpathIndex(component)));
352
353        }
354
355    }
356
357    /** Attribute name for configuration string. */
358    public static final String A_CONFIGURATION = "configuration";
359
360    /** Constant for the "appinfo" element name itself. */
361    public static final String APPINFO_APPINFO = "appinfo";
362
363    /** Constant for the "addto" appinfo attribute name. */
364    public static final String APPINFO_ATTR_ADD_TO = "addto";
365
366    /** Constant for the "boost" appinfo attribute name. */
367    public static final String APPINFO_ATTR_BOOST = "boost";
368
369    /** Constant for the "class" appinfo attribute name. */
370    public static final String APPINFO_ATTR_CLASS = "class";
371
372    /** Constant for the "collapse" appinfo attribute name. */
373    public static final String APPINFO_ATTR_COLLAPSE = "collapse";
374
375    /** Constant for the "configuration" appinfo attribute name. */
376    public static final String APPINFO_ATTR_CONFIGURATION = "configuration";
377
378    /** The exclude from index attribute. */
379    public static final String APPINFO_ATTR_CONTAINER_PAGE_ONLY = "containerPageOnly";
380
381    /** Constant for the "copyfields" appinfo attribute name. */
382    public static final String APPINFO_ATTR_COPY_FIELDS = "copyfields";
383
384    /** Constant for the "default" appinfo attribute name. */
385    public static final String APPINFO_ATTR_DEFAULT = "default";
386
387    /** Constant for the "description" appinfo attribute name. */
388    public static final String APPINFO_ATTR_DESCRIPTION = "description";
389
390    /** Constant for the "displaycompact" appinfo attribute name. */
391    public static final String APPINFO_ATTR_DISPLAY = "display";
392
393    /** Constant for the "element" appinfo attribute name. */
394    public static final String APPINFO_ATTR_ELEMENT = "element";
395
396    /** Constant for the "error" appinfo attribute name. */
397    public static final String APPINFO_ATTR_ERROR = "error";
398
399    /** Constant for the "invalidate" appinfo attribute name. */
400    public static final String APPINFO_ATTR_INVALIDATE = "invalidate";
401
402    /** Constant for the "key" appinfo attribute name. */
403    public static final String APPINFO_ATTR_KEY = "key";
404
405    /** Constant for the "locale" appinfo attribute name. */
406    public static final String APPINFO_ATTR_LOCALE = "locale";
407
408    /** Constant for the "mapping" appinfo attribute name. */
409    public static final String APPINFO_ATTR_MAPPING = "mapping";
410
411    /** Constant for the "mapto" appinfo attribute name. */
412    public static final String APPINFO_ATTR_MAPTO = "mapto";
413
414    /** Constant for the "maxwidth" appinfo attribute name. */
415    public static final String APPINFO_ATTR_MAXWIDTH = "maxwidth";
416
417    /** Constant for the "message" appinfo attribute name. */
418    public static final String APPINFO_ATTR_MESSAGE = "message";
419
420    /** Constant for the "minwidth" appinfo attribute name. */
421    public static final String APPINFO_ATTR_MINWIDTH = "minwidth";
422
423    /** Constant for the "name" appinfo attribute name. */
424    public static final String APPINFO_ATTR_NAME = "name";
425
426    /** Constant for the "nice-name" appinfo attribute name. */
427    public static final String APPINFO_ATTR_NICE_NAME = "nice-name";
428
429    /** Constant for the "params" appinfo attribute name. */
430    public static final String APPINFO_ATTR_PARAMS = "params";
431
432    /** Constant for the "preview" appinfo attribute name. */
433    public static final String APPINFO_ATTR_PREVIEW = "preview";
434
435    /** Constant for the "regex" appinfo attribute name. */
436    public static final String APPINFO_ATTR_REGEX = "regex";
437
438    /** Constant for the "resolveMacros" attribute name. */
439    public static final String APPINFO_ATTR_RESOLVE_MACROS = "resolveMacros";
440
441    /** Constant for the "rule-regex" appinfo attribute name. */
442    public static final String APPINFO_ATTR_RULE_REGEX = "rule-regex";
443
444    /** Constant for the "rule-type" appinfo attribute name. */
445    public static final String APPINFO_ATTR_RULE_TYPE = "rule-type";
446
447    /** Constant for the "scope" appinfo attribute name. */
448    public static final String APPINFO_ATTR_SCOPE = "scope";
449
450    /** Constant for the "searchcontent" appinfo attribute name. */
451    public static final String APPINFO_ATTR_SEARCHCONTENT = "searchcontent";
452
453    /** Constant for the "select-inherit" appinfo attribute name. */
454    public static final String APPINFO_ATTR_SELECT_INHERIT = "select-inherit";
455
456    /** Constant for the "sourcefield" appinfo attribute name. */
457    public static final String APPINFO_ATTR_SOURCE_FIELD = "sourcefield";
458
459    /** Constant for the "targetfield" appinfo attribute name. */
460    public static final String APPINFO_ATTR_TARGET_FIELD = "targetfield";
461
462    /** Constant for the "type" appinfo attribute name. */
463    public static final String APPINFO_ATTR_TYPE = "type";
464
465    /** Constant for the "node" appinfo attribute value. */
466    public static final String APPINFO_ATTR_TYPE_NODE = "node";
467
468    /** Constant for the "parent" appinfo attribute value. */
469    public static final String APPINFO_ATTR_TYPE_PARENT = "parent";
470
471    /** Constant for the "warning" appinfo attribute value. */
472    public static final String APPINFO_ATTR_TYPE_WARNING = "warning";
473
474    /** Constant for the "uri" appinfo attribute name. */
475    public static final String APPINFO_ATTR_URI = "uri";
476
477    /** Constant for the "useall" appinfo attribute name. */
478    public static final String APPINFO_ATTR_USEALL = "useall";
479
480    /** Constant for the "value" appinfo attribute name. */
481    public static final String APPINFO_ATTR_VALUE = "value";
482
483    /** Constant for the "widget" appinfo attribute name. */
484    public static final String APPINFO_ATTR_WIDGET = "widget";
485
486    /** Constant for the "widget-config" appinfo attribute name. */
487    public static final String APPINFO_ATTR_WIDGET_CONFIG = "widget-config";
488
489    /** Constant for formatter include resource type 'CSS'. */
490    public static final String APPINFO_ATTRIBUTE_TYPE_CSS = "css";
491
492    /** Constant for formatter include resource type 'JAVASCRIPT'. */
493    public static final String APPINFO_ATTRIBUTE_TYPE_JAVASCRIPT = "javascript";
494
495    /** Constant for the "bundle" appinfo element name. */
496    public static final String APPINFO_BUNDLE = "bundle";
497
498    /** Constant for the "default" appinfo element name. */
499    public static final String APPINFO_DEFAULT = "default";
500
501    /** Constant for the "defaults" appinfo element name. */
502    public static final String APPINFO_DEFAULTS = "defaults";
503
504    /** Constant for the "edithandler" appinfo element name. */
505    public static final String APPINFO_EDIT_HANDLER = "edithandler";
506
507    /** Constant for the "editorchangehandler" appinfo element name. */
508    public static final String APPINFO_EDITOR_CHANGE_HANDLER = "editorchangehandler";
509
510    /** Constant for the "editorchangehandlers" appinfo element name. */
511    public static final String APPINFO_EDITOR_CHANGE_HANDLERS = "editorchangehandlers";
512
513    /** Constant for the "forbidden-contexts" appinfo attribute name. */
514    public static final String APPINFO_FORBIDDEN_CONTEXTS = "forbidden-contexts";
515
516    /** Constant for the "formatter" appinfo element name. */
517    public static final String APPINFO_FORMATTER = "formatter";
518
519    /** Constant for the "formatters" appinfo element name. */
520    public static final String APPINFO_FORMATTERS = "formatters";
521
522    /** Constant for the 'geomapping' node. */
523    public static final String APPINFO_GEOMAPPING = "geomapping";
524
525    /** Constant for the "headinclude" appinfo element name. */
526    public static final String APPINFO_HEAD_INCLUDE = "headinclude";
527
528    /** Constant for the "headincludes" appinfo element name. */
529    public static final String APPINFO_HEAD_INCLUDES = "headincludes";
530
531    /** Constant for the "layout" appinfo element name. */
532    public static final String APPINFO_LAYOUT = "layout";
533
534    /** Constant for the "layouts" appinfo element name. */
535    public static final String APPINFO_LAYOUTS = "layouts";
536
537    /** Constant for the "mapping" appinfo element name. */
538    public static final String APPINFO_MAPPING = "mapping";
539
540    /** Constant for the "mappings" appinfo element name. */
541    public static final String APPINFO_MAPPINGS = "mappings";
542
543    /** Constant for the 'messagekeyhandler' node. */
544    public static final String APPINFO_MESSAGEKEYHANDLER = "messagekeyhandler";
545
546    /** Constant for the "modelfolder" appinfo element name. */
547    public static final String APPINFO_MODELFOLDER = "modelfolder";
548
549    /** Constant for the "nestedformatter" appinfo element name. */
550    public static final String APPINFO_NESTED_FORMATTER = "nestedformatter";
551
552    /** Constant for the "nestedformatters" appinfo element name. */
553    public static final String APPINFO_NESTED_FORMATTERS = "nestedformatters";
554
555    /** Constant for the "param" appinfo attribute name. */
556    public static final String APPINFO_PARAM = "param";
557
558    /** Constant for the "parameters" appinfo element name. */
559    public static final String APPINFO_PARAMETERS = "parameters";
560
561    /** version-transformation node name. */
562    public static final String APPINFO_VERSION_TRANSFORMATION = "versiontransformation";
563
564    /** Constant for the "preview" appinfo element name. */
565    public static final String APPINFO_PREVIEW = "preview";
566
567    /** Constant for the "propertybundle" appinfo element name. */
568    public static final String APPINFO_PROPERTYBUNDLE = "propertybundle";
569
570    /** Constant for the "relation" appinfo element name. */
571    public static final String APPINFO_RELATION = "relation";
572
573    /** Constant for the "relations" appinfo element name. */
574    public static final String APPINFO_RELATIONS = "relations";
575
576    /** Constant for the "resource" appinfo element name. */
577    public static final String APPINFO_RESOURCE = "resource";
578
579    /** Constant for the "resourcebundle" appinfo element name. */
580    public static final String APPINFO_RESOURCEBUNDLE = "resourcebundle";
581
582    /** Constant for the "resourcebundles" appinfo element name. */
583    public static final String APPINFO_RESOURCEBUNDLES = "resourcebundles";
584
585    /** Constant for the reverse-mapping-enabled appinfo element name. */
586    public static final String APPINFO_REVERSE_MAPPING_ENABLED = "reverse-mapping-enabled";
587
588    /** Constant for the "rule" appinfo element name. */
589    public static final String APPINFO_RULE = "rule";
590
591    /** The file where the default appinfo schema is located. */
592    public static final String APPINFO_SCHEMA_FILE = "org/opencms/xml/content/DefaultAppinfo.xsd";
593
594    /** The file where the default appinfo schema types are located. */
595    public static final String APPINFO_SCHEMA_FILE_TYPES = "org/opencms/xml/content/DefaultAppinfoTypes.xsd";
596
597    /** The XML system id for the default appinfo schema types. */
598    public static final String APPINFO_SCHEMA_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX
599        + APPINFO_SCHEMA_FILE;
600
601    /** The XML system id for the default appinfo schema types. */
602    public static final String APPINFO_SCHEMA_TYPES_SYSTEM_ID = CmsConfigurationManager.DEFAULT_DTD_PREFIX
603        + APPINFO_SCHEMA_FILE_TYPES;
604
605    /** Constant for the "searchsetting" appinfo element name. */
606    public static final String APPINFO_SEARCHSETTING = "searchsetting";
607
608    /** Constant for the "searchsettings" appinfo element name. */
609    public static final String APPINFO_SEARCHSETTINGS = "searchsettings";
610
611    /** Constant for the "setting" appinfo element name. */
612    public static final String APPINFO_SETTING = "setting";
613
614    /** Constant for the "settings" appinfo element name. */
615    public static final String APPINFO_SETTINGS = "settings";
616
617    /** Constant for the "solrfield" appinfo element name. */
618    public static final String APPINFO_SOLR_FIELD = "solrfield";
619
620    /** Constant for the "synchronization" appinfo element name. */
621    public static final String APPINFO_SYNCHRONIZATION = "synchronization";
622
623    /** Constant for the "synchronizations" appinfo element name. */
624    public static final String APPINFO_SYNCHRONIZATIONS = "synchronizations";
625
626    /** Constant for the "tab" appinfo element name. */
627    public static final String APPINFO_TAB = "tab";
628
629    /** Constant for the "tabs" appinfo element name. */
630    public static final String APPINFO_TABS = "tabs";
631
632    /** Node name. */
633    public static final String APPINFO_TEMPLATE = "template";
634
635    /** Node name. */
636    public static final String APPINFO_TEMPLATES = "templates";
637
638    /** Constant for the "validationrule" appinfo element name. */
639    public static final String APPINFO_VALIDATIONRULE = "validationrule";
640
641    /** Constant for the "validationrules" appinfo element name. */
642    public static final String APPINFO_VALIDATIONRULES = "validationrules";
643
644    /** Constant for the "element" value of the appinfo attribute "addto". */
645    public static final String APPINFO_VALUE_ADD_TO_CONTENT = "element";
646
647    /** Constant for the "page" value of the appinfo attribute "addto". */
648    public static final String APPINFO_VALUE_ADD_TO_PAGE = "page";
649
650    /** Constant for the "visibilities" appinfo element name. */
651    public static final String APPINFO_VISIBILITIES = "visibilities";
652
653    /** Constant for the "visibility" appinfo element name. */
654    public static final String APPINFO_VISIBILITY = "visibility";
655
656    /** Constant for the "xmlbundle" appinfo element name. */
657    public static final String APPINFO_XMLBUNDLE = "xmlbundle";
658
659    /** Attribute name. */
660    public static final String ATTR_ENABLED = "enabled";
661
662    /** Attribute name. */
663    public static final String ATTR_ENABLED_BY_DEFAULT = "enabledByDefault";
664
665    /** Attribute name. */
666    public static final String ATTR_USE_ACACIA = "useAcacia";
667
668    /** Constant for head include type attribute: CSS. */
669    public static final String ATTRIBUTE_INCLUDE_TYPE_CSS = "css";
670
671    /** Constant for head include type attribute: java-script. */
672    public static final String ATTRIBUTE_INCLUDE_TYPE_JAVASCRIPT = "javascript";
673
674    /** Field for mapping geo-coordinates. */
675    public static final String GEOMAPPING_FIELD = "geocoords_loc";
676
677    /** Macro for resolving the preview URI. */
678    public static final String MACRO_PREVIEW_TEMPFILE = "previewtempfile";
679
680    /** Node name for change handler. */
681    public static final String N_CHANGEHANDLER = "ChangeHandler";
682
683    /** Constant for the 'Setting' node name. */
684    public static final String N_SETTING = "Setting";
685
686    /** Default message for validation errors. */
687    protected static final String MESSAGE_VALIDATION_DEFAULT_ERROR = "${validation.path}: "
688        + "${key."
689        + Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_ERROR_2
690        + "|${validation.value}|[${validation.regex}]}";
691
692    /** Default message for validation warnings. */
693    protected static final String MESSAGE_VALIDATION_DEFAULT_WARNING = "${validation.path}: "
694        + "${key."
695        + Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_WARNING_2
696        + "|${validation.value}|[${validation.regex}]}";
697
698    /** Set of xpath indexes that allow reverse availability mappings. */
699    static Set<String> indexesNotSetOrOne = new HashSet<>(Arrays.asList("", "[1]"));
700
701    /** The attribute name for the "prefer folder" option for properties. */
702    private static final String APPINFO_ATTR_PREFERFOLDER = "PreferFolder";
703
704    /** The 'useDefault' attribute name. */
705    private static final String APPINFO_ATTR_USE_DEFAULT = "useDefault";
706
707    /** The node name for the default complex widget configuration. */
708    private static final Object APPINFO_DEFAULTWIDGET = "defaultwidget";
709
710    /** Node name for the list of field declarations. */
711    private static final Object APPINFO_FIELD_SETTINGS = "FieldSettings";
712
713    /** JSON renderer node name. */
714    private static final Object APPINFO_JSON_RENDERER = "jsonrenderer";
715
716    /** Attribute name for the context used for resolving content mappings. */
717    private static final String ATTR_MAPPING_RESOLUTION_CONTEXT = "MAPPING_RESOLUTION_CONTEXT";
718
719    /** The log object for this class. */
720    private static final Log LOG = CmsLog.getLog(CmsDefaultXmlContentHandler.class);
721
722    /** The principal list separator. */
723    private static final String PRINCIPAL_LIST_SEPARATOR = ",";
724
725    /** The title property individual mapping key. */
726    private static final String TITLE_PROPERTY_INDIVIDUAL_MAPPING = MAPTO_PROPERTY_INDIVIDUAL
727        + CmsPropertyDefinition.PROPERTY_TITLE;
728
729    /** The title property mapping key. */
730    private static final String TITLE_PROPERTY_MAPPING = MAPTO_PROPERTY + CmsPropertyDefinition.PROPERTY_TITLE;
731
732    /** The title property shared mapping key. */
733    private static final String TITLE_PROPERTY_SHARED_MAPPING = MAPTO_PROPERTY_SHARED
734        + CmsPropertyDefinition.PROPERTY_TITLE;
735
736    /**
737     * Static initializer for caching the default appinfo validation schema.<p>
738     */
739    static {
740
741        // the schema definition is located in 2 separates file for easier editing
742        // 2 files are required in case an extended schema want to use the default definitions,
743        // but with an extended "appinfo" node
744        byte[] appinfoSchemaTypes;
745        try {
746            // first read the default types
747            appinfoSchemaTypes = CmsFileUtil.readFile(APPINFO_SCHEMA_FILE_TYPES);
748        } catch (Exception e) {
749            throw new CmsRuntimeException(
750                Messages.get().container(
751                    org.opencms.xml.types.Messages.ERR_XMLCONTENT_LOAD_SCHEMA_1,
752                    APPINFO_SCHEMA_FILE_TYPES),
753                e);
754        }
755        CmsXmlEntityResolver.cacheSystemId(APPINFO_SCHEMA_TYPES_SYSTEM_ID, appinfoSchemaTypes);
756        byte[] appinfoSchema;
757        try {
758            // now read the default base schema
759            appinfoSchema = CmsFileUtil.readFile(APPINFO_SCHEMA_FILE);
760        } catch (Exception e) {
761            throw new CmsRuntimeException(
762                Messages.get().container(
763                    org.opencms.xml.types.Messages.ERR_XMLCONTENT_LOAD_SCHEMA_1,
764                    APPINFO_SCHEMA_FILE),
765                e);
766        }
767        CmsXmlEntityResolver.cacheSystemId(APPINFO_SCHEMA_SYSTEM_ID, appinfoSchema);
768    }
769
770    /** The set of allowed templates. */
771    protected CmsDefaultSet<String> m_allowedTemplates = new CmsDefaultSet<String>();
772
773    /** The configuration values for the element widgets (as defined in the annotations). */
774    protected Map<String, String> m_configurationValues;
775
776    /** The CSS resources to include into the html-page head. */
777    protected Set<String> m_cssHeadIncludes;
778
779    /** The default values for the elements (as defined in the annotations). */
780    protected Map<String, String> m_defaultValues;
781
782    /** The element mappings (as defined in the annotations). */
783    protected Map<String, List<String>> m_elementMappings;
784
785    /** The formatter configuration. */
786    protected CmsFormatterConfiguration m_formatterConfiguration;
787
788    /** The list of formatters from the XSD. */
789    protected List<CmsFormatterBean> m_formatters;
790
791    /** The configured geo-coordinate mapping configuration entries. */
792    protected List<CmsGeoMappingConfiguration.Entry> m_geomappingEntries = new ArrayList<>();
793
794    /** Relation actions. */
795    protected Map<String, InvalidRelationAction> m_invalidRelationActions = new HashMap<>();
796
797    /** The java-script resources to include into the html-page head. */
798    protected Set<String> m_jsHeadIncludes;
799
800    /** The resource bundle name to be used for localization of this content handler. */
801    protected List<String> m_messageBundleNames;
802
803    /** The folder containing the model file(s) for the content. */
804    protected String m_modelFolder;
805
806    /** The preview location (as defined in the annotations). */
807    protected String m_previewLocation;
808
809    /** Name of the field used for geo-coordinate mapping. */
810    protected String m_primaryGeomappingField;
811
812    /** The relation check rules. */
813    protected Map<String, Boolean> m_relationChecks;
814
815    /** The relation check rules. */
816    protected Map<String, CmsRelationType> m_relations;
817
818    /** The Solr field configurations. */
819    protected Map<String, CmsSearchField> m_searchFields;
820
821    /** The Solr field configurations added to the container pages contents are on. */
822    protected Map<String, CmsSearchField> m_searchFieldsPage;
823
824    /** The search settings. */
825    protected Map<String, SearchContentType> m_searchSettings;
826
827    /** String template group for the simple search setting expansions. */
828    protected StringTemplateGroup m_searchTemplateGroup;
829
830    /** The configured settings for the formatters (as defined in the annotations). */
831    protected Map<String, CmsXmlContentProperty> m_settings;
832
833    /** Path to XSL transform in VFS to use for version transformation. */
834    protected String m_versionTransformation;
835
836    /** The configured locale synchronization elements. */
837    protected List<String> m_synchronizations;
838
839    /** The configured tabs. */
840    protected List<CmsXmlContentTab> m_tabs;
841
842    /** The list of mappings to the "Title" property. */
843    protected List<String> m_titleMappings;
844
845    /** Flag which controls whether the Acacia editor should be disabled for this type. */
846    protected boolean m_useAcacia = true;
847
848    /** The messages for the error validation rules. */
849    protected Map<String, String> m_validationErrorMessages;
850
851    /** The validation rules that cause an error (as defined in the annotations). */
852    protected Map<String, String> m_validationErrorRules;
853
854    /** The messages for the warning validation rules. */
855    protected Map<String, String> m_validationWarningMessages;
856
857    /** The validation rules that cause a warning (as defined in the annotations). */
858    protected Map<String, String> m_validationWarningRules;
859
860    /** Change handler configurations. */
861    private List<CmsChangeHandlerConfig> m_changeHandlerConfigs = new ArrayList<>();
862
863    /** The container page only flag, indicating if this XML content should be indexed on container pages only. */
864    private boolean m_containerPageOnly;
865
866    /** The content definition for which this content handler is configured. */
867    private CmsXmlContentDefinition m_contentDefinition;
868
869    /** The default complex widget class name. */
870    private String m_defaultWidget;
871
872    /** The default complex widget configuration. */
873    private String m_defaultWidgetConfig;
874
875    /** The default complex widget for this type. */
876    private I_CmsComplexWidget m_defaultWidgetInstance;
877
878    /** The elements to display in ncompact view. */
879    private HashMap<String, DisplayType> m_displayTypes;
880
881    /** An optional edit handler. */
882    private I_CmsEditHandler m_editHandler;
883
884    /** The editor change handlers. */
885    private List<I_CmsXmlContentEditorChangeHandler> m_editorChangeHandlers;
886
887    /** The descriptions for the fields. */
888    private Map<String, String> m_fieldDescriptions = new HashMap<>();
889
890    /** The nice names for the fields. */
891    private Map<String, String> m_fieldNiceNames = new HashMap<>();
892
893    /** Cached boolean indicating whether the content has category widgets. */
894    private volatile Boolean m_hasCategoryWidget;
895
896    /** The JSON renderer settings. */
897    private JsonRendererSettings m_jsonRendererSettings;
898
899    /** A set of keys identifying the mappings which should use default values if the corresponding values are not set in the XML content. */
900    private Set<String> m_mappingsUsingDefault = new HashSet<String>();
901
902    /** Message key fallback handler for the editor. */
903    private CmsMultiMessages.I_KeyFallbackHandler m_messageKeyHandler = new CmsMultiMessages.I_KeyFallbackHandler() {
904
905        public Optional<String> getFallbackKey(String key) {
906
907            return Optional.absent();
908        }
909    };
910
911    /** The nested formatter elements. */
912    private Set<String> m_nestedFormatterElements;
913
914    /** The paths of values for which no macros should be resolved when getting the default value. */
915    private Set<String> m_nonMacroResolvableDefaults = new HashSet<String>();
916
917    /** The parameters. */
918    private CmsParameterConfiguration m_parameters = new CmsParameterConfiguration();
919
920    /** Option to disable reverse mapping for this content type. */
921    private boolean m_reverseMappingEnabled = true;
922
923    /** The visibility configurations by element path. */
924    private Map<String, VisibilityConfiguration> m_visibilityConfigurations = new HashMap<String, VisibilityConfiguration>();
925
926    /** The map of widget names by path. */
927    private Map<String, String> m_widgetNames = new HashMap<>();
928
929    /**
930     * Creates a new instance of the default XML content handler.<p>
931     */
932    public CmsDefaultXmlContentHandler() {
933
934        init();
935    }
936
937    /**
938     * Collects change handler confiugrations for all nested contents.
939     *
940     * @param contentDef the content definition
941     * @param parentPath the parent path
942     * @param result the multimap to collect the handler configurations in, with the key being the path of the nested content in whose schema they are configured
943     */
944    private static void collectNestedChangeHandlerConfigs(
945        CmsXmlContentDefinition contentDef,
946        String parentPath,
947        Multimap<String, CmsChangeHandlerConfig> result) {
948
949        I_CmsXmlContentHandler handler = contentDef.getContentHandler();
950        List<CmsChangeHandlerConfig> handlerConfigs = handler.getChangeHandlerConfigs();
951        for (CmsChangeHandlerConfig handlerConfig : handlerConfigs) {
952            result.put(parentPath, handlerConfig);
953        }
954
955        for (I_CmsXmlSchemaType schemaType : contentDef.getTypeSequence()) {
956            String name = schemaType.getName();
957            if (schemaType instanceof CmsXmlNestedContentDefinition) {
958                CmsXmlNestedContentDefinition nested = (CmsXmlNestedContentDefinition)schemaType;
959                collectNestedChangeHandlerConfigs(nested.getNestedContentDefinition(), parentPath + "/" + name, result);
960            }
961        }
962    }
963
964    /**
965     * Gets the invalid relation action for the given value.
966     * @param value the value
967     * @return the invalid relation action
968     */
969    private static InvalidRelationAction getInvalidRelationActionForValue(I_CmsXmlContentValue value) {
970
971        try {
972            String path = value.getPath();
973            String simpleName = CmsXmlUtils.getLastXpathElement(path);
974            return value.getContentDefinition().getContentHandler().getInvalidRelationAction(simpleName);
975        } catch (Exception e) {
976            LOG.error(e.getLocalizedMessage(), e);
977            return null;
978        }
979    }
980
981    /**
982     * Makes a path suitable for use as a change handler scope by appending wildcards to every path segment.
983     *
984     * @param path the path to process
985     * @return the scope for the editor change handler
986     */
987    private static String normalizeChangeHandlerScope(String path) {
988
989        List<String> normalizedKeyParts = new ArrayList<>();
990        // Append wildcard to every path component that doesn't end with a wildcard or index
991        for (String keyPart : path.split("/")) {
992            String normalizedKeyPart = null;
993            if (keyPart.endsWith("*") || keyPart.endsWith("]")) {
994                normalizedKeyPart = keyPart;
995            } else {
996                normalizedKeyPart = keyPart + "*";
997            }
998            normalizedKeyParts.add(normalizedKeyPart);
999        }
1000        String normalizedKey = CmsStringUtil.listAsString(normalizedKeyParts, "/");
1001        return normalizedKey;
1002
1003    }
1004
1005    /**
1006     * @see org.opencms.xml.content.I_CmsXmlContentHandler#applyReverseAvailabilityMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.xml.content.CmsMappingResolutionContext.AttributeType, java.util.List, long)
1007     */
1008    public boolean applyReverseAvailabilityMapping(
1009        CmsObject cms,
1010        CmsXmlContent content,
1011        AttributeType attr,
1012        List<Locale> resourceLocales,
1013        long valueToSet) {
1014
1015        MappingInfo info = getAttributeMapping(attr);
1016        if (!info.canBeUsedForReverseAvailabilityMapping()) {
1017            return false;
1018        }
1019
1020        long defaultValue = -1;
1021        switch (attr) {
1022            case expiration:
1023                defaultValue = CmsResource.DATE_EXPIRED_DEFAULT;
1024                break;
1025            case release:
1026                defaultValue = CmsResource.DATE_RELEASED_DEFAULT;
1027                break;
1028            default:
1029                return false;
1030        }
1031        String mappedElement = info.getSource();
1032
1033        I_CmsXmlSchemaType type = m_contentDefinition.getSchemaType(mappedElement);
1034        // change value in the first locale from the list of resource locales that we have in the content
1035        List<Locale> localesToProcess = Collections.emptyList();
1036        for (Locale locale : resourceLocales) {
1037            if (content.hasLocale(locale)) {
1038                localesToProcess = Collections.singletonList(locale);
1039                break;
1040            }
1041        }
1042        if (localesToProcess.size() > 0) {
1043            Locale locale = localesToProcess.get(0);
1044            if (content.hasValue(mappedElement, locale)) {
1045                I_CmsXmlContentValue value = content.getValue(mappedElement, locale);
1046                String stringValue = value.getStringValue(cms);
1047                if (stringValue.contains("%")) {
1048                    LOG.debug(
1049                        content.getFile().getRootPath()
1050                            + ": Didn't apply reverse availability mapping because of macro value "
1051                            + stringValue);
1052                    return false;
1053                }
1054                if (valueToSet == defaultValue) {
1055                    if (type.getMinOccurs() == 0) {
1056                        content.removeValue(mappedElement, locale, 0);
1057                    } else if (type instanceof CmsXmlStringValue) {
1058                        content.getValue(mappedElement, locale).setStringValue(cms, "");
1059                    } else {
1060                        LOG.warn(
1061                            content.getFile().getRootPath()
1062                                + ": Could not apply reverse availability mapping because the field "
1063                                + mappedElement
1064                                + " is neither optional nor of type OpenCmsString.");
1065                    }
1066                } else {
1067                    content.getValue(mappedElement, locale).setStringValue(cms, "" + valueToSet);
1068                }
1069            } else if (valueToSet != defaultValue) {
1070                Set<String> parentSet = new HashSet<>();
1071                String currentPath = mappedElement;
1072                while (!parentSet.contains(currentPath)) {
1073                    parentSet.add(currentPath);
1074                    currentPath = CmsXmlUtils.removeLastXpathElement(currentPath);
1075                }
1076                List<String> sortedParents = new ArrayList<>(parentSet);
1077                Collections.sort(sortedParents);
1078                for (String parent : sortedParents) {
1079                    if (!content.hasValue(parent, locale)) {
1080                        content.addValue(cms, parent, locale, 0);
1081                    }
1082                }
1083                content.getValue(mappedElement, locale).setStringValue(cms, "" + valueToSet);
1084            }
1085        }
1086        return true;
1087    }
1088
1089    /**
1090     * @see org.opencms.xml.content.I_CmsXmlContentHandler#canUseReverseAvailabilityMapping(org.opencms.xml.content.CmsMappingResolutionContext.AttributeType)
1091     */
1092    public boolean canUseReverseAvailabilityMapping(AttributeType attr) {
1093
1094        if (!m_reverseMappingEnabled) {
1095            return false;
1096        }
1097
1098        MappingInfo info = getAttributeMapping(attr);
1099        return info.canBeUsedForReverseAvailabilityMapping();
1100
1101    }
1102
1103    /**
1104     * Copies a given CMS context and set the copy's site root to '/'.<p>
1105     *
1106     * @param cms the CMS context to copy
1107     * @return the copy
1108     *
1109     * @throws CmsException if something goes wrong
1110     */
1111    public CmsObject createRootCms(CmsObject cms) throws CmsException {
1112
1113        CmsObject rootCms = OpenCms.initCmsObject(cms);
1114        Object logEntry = cms.getRequestContext().getAttribute(CmsLogEntry.ATTR_LOG_ENTRY);
1115        if (logEntry != null) {
1116            rootCms.getRequestContext().setAttribute(CmsLogEntry.ATTR_LOG_ENTRY, logEntry);
1117        }
1118        rootCms.getRequestContext().setSiteRoot("/");
1119        return rootCms;
1120    }
1121
1122    /**
1123     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getAllowedTemplates()
1124     */
1125    public CmsDefaultSet<String> getAllowedTemplates() {
1126
1127        return m_allowedTemplates;
1128
1129    }
1130
1131    /**
1132     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getChangeHandlerConfigs()
1133     */
1134    public List<CmsChangeHandlerConfig> getChangeHandlerConfigs() {
1135
1136        return Collections.unmodifiableList(m_changeHandlerConfigs);
1137    }
1138
1139    /**
1140     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getComplexWidget(org.opencms.file.CmsObject, java.lang.String)
1141     */
1142    public I_CmsComplexWidget getComplexWidget(CmsObject cms, String path) {
1143
1144        String widgetName = m_widgetNames.get(path);
1145        if (widgetName == null) {
1146            return null;
1147        }
1148        if (cms != null) {
1149            CmsMacroResolver resolver = new CmsMacroResolver();
1150            resolver.setCmsObject(cms);
1151            widgetName = resolver.resolveMacros(widgetName);
1152        }
1153        if (CmsStringUtil.isValidJavaClassName(widgetName)) {
1154            try {
1155                Class<?> cls = Class.forName(widgetName, false, getClass().getClassLoader());
1156                if (I_CmsComplexWidget.class.isAssignableFrom(cls)) {
1157                    return (I_CmsComplexWidget)(cls.newInstance());
1158                }
1159            } catch (Exception e) {
1160                LOG.warn(e.getLocalizedMessage(), e);
1161                return null;
1162            }
1163        }
1164        return null;
1165
1166    }
1167
1168    /**
1169     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getConfiguration(org.opencms.xml.types.I_CmsXmlSchemaType)
1170     */
1171    public String getConfiguration(I_CmsXmlSchemaType type) {
1172
1173        String elementName = type.getName();
1174        return m_configurationValues.get(elementName);
1175
1176    }
1177
1178    /**
1179     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getConfiguration(org.opencms.xml.types.I_CmsXmlSchemaType)
1180     */
1181    public String getConfiguration(String path) {
1182
1183        return m_configurationValues.get(path);
1184
1185    }
1186
1187    /**
1188     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getConfiguredDisplayType(java.lang.String, org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType)
1189     */
1190    public DisplayType getConfiguredDisplayType(String path, DisplayType defaultValue) {
1191
1192        DisplayType result = m_displayTypes.get(path);
1193        if (result == null) {
1194            result = defaultValue;
1195        }
1196        return result;
1197
1198    }
1199
1200    /**
1201     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getCSSHeadIncludes()
1202     */
1203    public Set<String> getCSSHeadIncludes() {
1204
1205        return Collections.unmodifiableSet(m_cssHeadIncludes);
1206    }
1207
1208    /***
1209     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getCSSHeadIncludes(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
1210     */
1211    @SuppressWarnings("unused")
1212    public Set<String> getCSSHeadIncludes(CmsObject cms, CmsResource resource) throws CmsException {
1213
1214        return getCSSHeadIncludes();
1215    }
1216
1217    /**
1218     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefault(org.opencms.file.CmsObject, org.opencms.file.CmsResource, org.opencms.xml.types.I_CmsXmlSchemaType, java.lang.String, java.util.Locale)
1219     */
1220    public String getDefault(CmsObject cms, CmsResource resource, I_CmsXmlSchemaType type, String path, Locale locale) {
1221
1222        String defaultValue;
1223        if (CmsStringUtil.isEmptyOrWhitespaceOnly(path)) {
1224            // ( path can be empty if this is called from createValue )
1225            // use the "getDefault" method of the given value, will use value from standard XML schema
1226            defaultValue = type.getDefault(locale);
1227        } else {
1228            // look up the default from the configured mappings
1229            defaultValue = m_defaultValues.get(path);
1230            if (defaultValue == null) {
1231                // no value found, try default xpath
1232                path = CmsXmlUtils.removeXpath(path);
1233                path = CmsXmlUtils.createXpath(path, 1);
1234                // look up the default value again with default index of 1 in all path elements
1235                defaultValue = m_defaultValues.get(path);
1236            }
1237        }
1238        if (defaultValue != null) {
1239            CmsObject newCms = cms;
1240            if (resource != null) {
1241                try {
1242                    // switch the current URI to the XML document resource so that properties can be read
1243                    CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(resource.getRootPath());
1244                    if (site != null) {
1245                        newCms = OpenCms.initCmsObject(cms);
1246                        newCms.getRequestContext().setSiteRoot(site.getSiteRoot());
1247                        newCms.getRequestContext().setUri(newCms.getSitePath(resource));
1248                    }
1249                } catch (Exception e) {
1250                    // on any error just use the default input OpenCms context
1251                }
1252            }
1253            // return the default value with processed macros
1254            String result = defaultValue;
1255            if (!m_nonMacroResolvableDefaults.contains(path)) {
1256                CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(newCms).setMessages(
1257                    getMessages(locale));
1258                result = resolver.resolveMacros(defaultValue);
1259            }
1260            return result;
1261        } else if (!CmsStringUtil.isEmptyOrWhitespaceOnly(path) && CmsXmlUtils.isDeepXpath(path)) {
1262
1263            // try to delegate to content handler of nested content
1264
1265            String subPath = CmsXmlUtils.removeFirstXpathElement(path);
1266            I_CmsXmlSchemaType nestedType = m_contentDefinition.getSchemaType(
1267                CmsXmlUtils.removeXpath(CmsXmlUtils.getFirstXpathElement(path)));
1268            if (nestedType instanceof CmsXmlNestedContentDefinition) {
1269                CmsXmlContentDefinition nestedDef = ((CmsXmlNestedContentDefinition)nestedType).getNestedContentDefinition();
1270                if (nestedDef != null) {
1271                    I_CmsXmlContentHandler subHandler = nestedDef.getContentHandler();
1272                    if (subHandler != null) {
1273                        return subHandler.getDefault(cms, resource, nestedType, subPath, locale);
1274                    }
1275                }
1276            }
1277        }
1278        // no default value is available
1279        return null;
1280    }
1281
1282    /**
1283     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefault(org.opencms.file.CmsObject, I_CmsXmlContentValue, java.util.Locale)
1284     */
1285    public String getDefault(CmsObject cms, I_CmsXmlContentValue value, Locale locale) {
1286
1287        String path = null;
1288        if (value.getElement() != null) {
1289            path = value.getPath();
1290        }
1291
1292        return getDefault(cms, value.getDocument() != null ? value.getDocument().getFile() : null, value, path, locale);
1293    }
1294
1295    /**
1296     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefaultComplexWidget()
1297     */
1298    public I_CmsComplexWidget getDefaultComplexWidget() {
1299
1300        return m_defaultWidgetInstance;
1301    }
1302
1303    /**
1304     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefaultComplexWidgetClass()
1305     */
1306    public String getDefaultComplexWidgetClass() {
1307
1308        return m_defaultWidget;
1309    }
1310
1311    /**
1312     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDefaultComplexWidgetConfiguration()
1313     */
1314    public String getDefaultComplexWidgetConfiguration() {
1315
1316        return m_defaultWidgetConfig;
1317    }
1318
1319    /**
1320     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getDisplayType(org.opencms.xml.types.I_CmsXmlSchemaType)
1321     */
1322    public DisplayType getDisplayType(I_CmsXmlSchemaType type) {
1323
1324        if (m_displayTypes.containsKey(type.getName())) {
1325            return m_displayTypes.get(type.getName());
1326        } else {
1327            return DisplayType.none;
1328        }
1329    }
1330
1331    /**
1332     * Returns the edit handler if configured.<p>
1333     *
1334     * @return the edit handler
1335     */
1336    public I_CmsEditHandler getEditHandler() {
1337
1338        return m_editHandler;
1339    }
1340
1341    /**
1342     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getEditorChangeHandlers(boolean)
1343     */
1344    public List<I_CmsXmlContentEditorChangeHandler> getEditorChangeHandlers(boolean selfOnly) {
1345
1346        if (selfOnly) {
1347            return Collections.unmodifiableList(m_editorChangeHandlers);
1348        } else {
1349            List<I_CmsXmlContentEditorChangeHandler> result = new ArrayList<>(m_editorChangeHandlers);
1350            List<I_CmsXmlContentEditorChangeHandler> nestedHandlers = getNestedEditorChangeHandlers();
1351            result.addAll(nestedHandlers);
1352            return result;
1353        }
1354    }
1355
1356    /**
1357     * Gets the help texts for the fields.<p>
1358     *
1359     * @return the help texts for the fields
1360     */
1361    public Map<String, String> getFieldHelp() {
1362
1363        return Collections.unmodifiableMap(m_fieldDescriptions);
1364    }
1365
1366    /**
1367     * Gets the labels for the fields.<p>
1368     *
1369     * @return the labels for the fields
1370     */
1371    public Map<String, String> getFieldLabels() {
1372
1373        return Collections.unmodifiableMap(m_fieldNiceNames);
1374    }
1375
1376    /**
1377     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getFormatterConfiguration(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
1378     */
1379    public CmsFormatterConfiguration getFormatterConfiguration(CmsObject cms, CmsResource resource) {
1380
1381        List<I_CmsFormatterBean> wrappers = Lists.newArrayList();
1382        for (CmsFormatterBean formatter : m_formatters) {
1383            CmsSchemaFormatterBeanWrapper wrapper = new CmsSchemaFormatterBeanWrapper(cms, formatter, this, resource);
1384            wrappers.add(wrapper);
1385        }
1386        return CmsFormatterConfiguration.create(cms, wrappers);
1387    }
1388
1389    /**
1390     * Gets the geo mapping configuration.
1391     *
1392     * @return the geo mapping configuration
1393     */
1394    public CmsGeoMappingConfiguration getGeoMappingConfiguration() {
1395
1396        if ((m_primaryGeomappingField == null) && (m_geomappingEntries.size() == 0)) {
1397            return null;
1398        }
1399        List<CmsGeoMappingConfiguration.Entry> configEntries = new ArrayList<>();
1400        if (m_primaryGeomappingField != null) {
1401            configEntries.add(new CmsGeoMappingConfiguration.Entry(EntryType.field, m_primaryGeomappingField));
1402        }
1403        configEntries.addAll(m_geomappingEntries);
1404        return new CmsGeoMappingConfiguration(configEntries);
1405    }
1406
1407    /**
1408     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getInvalidRelationAction(java.lang.String)
1409     */
1410    public InvalidRelationAction getInvalidRelationAction(String name) {
1411
1412        return m_invalidRelationActions.get(name);
1413    }
1414
1415    /**
1416     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getJSHeadIncludes()
1417     */
1418    public Set<String> getJSHeadIncludes() {
1419
1420        return Collections.<String> unmodifiableSet(m_jsHeadIncludes);
1421    }
1422
1423    /**
1424     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getJSHeadIncludes(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
1425     */
1426    @SuppressWarnings("unused")
1427    public Set<String> getJSHeadIncludes(CmsObject cms, CmsResource resource) throws CmsException {
1428
1429        return getJSHeadIncludes();
1430    }
1431
1432    /**
1433     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getJsonRendererSettings()
1434     */
1435    public JsonRendererSettings getJsonRendererSettings() {
1436
1437        return m_jsonRendererSettings;
1438    }
1439
1440    /**
1441     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getMappings()
1442     */
1443    public Map<String, List<String>> getMappings() {
1444
1445        Map<String, List<String>> result = new HashMap<>();
1446        for (Map.Entry<String, List<String>> entry : m_elementMappings.entrySet()) {
1447            result.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
1448        }
1449        return result;
1450    }
1451
1452    /**
1453     * Returns the all mappings defined for the given element xpath.<p>
1454     *
1455     * @since 7.0.2
1456     *
1457     * @param elementName the element xpath to look up the mapping for
1458     *
1459     * @return the mapping defined for the given element xpath
1460     */
1461    public List<String> getMappings(String elementName) {
1462
1463        List<String> result = m_elementMappings.get(elementName);
1464        if (result == null) {
1465            result = Collections.emptyList();
1466        }
1467        return result;
1468    }
1469
1470    /**
1471     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getMessageKeyHandler()
1472     */
1473    public I_KeyFallbackHandler getMessageKeyHandler() {
1474
1475        return m_messageKeyHandler;
1476    }
1477
1478    /**
1479     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getMessages(java.util.Locale)
1480     */
1481    public CmsMessages getMessages(Locale locale) {
1482
1483        CmsMessages result = null;
1484        if ((m_messageBundleNames == null) || m_messageBundleNames.isEmpty()) {
1485            return new CmsMessages(Messages.get().getBundleName(), locale);
1486        } else {
1487            // a message bundle was initialized
1488            CmsMultiMessages multiMessages = new CmsMultiMessages(locale);
1489            for (String messageBundleName : m_messageBundleNames) {
1490                multiMessages.addMessages(new CmsMessages(messageBundleName, locale));
1491            }
1492            if (!m_messageBundleNames.contains(Messages.get().getBundleName())) {
1493                multiMessages.addMessages(new CmsMessages(Messages.get().getBundleName(), locale));
1494            }
1495            result = multiMessages;
1496
1497        }
1498        return result;
1499    }
1500
1501    /**
1502     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getModelFolder()
1503     */
1504    public String getModelFolder() {
1505
1506        return m_modelFolder;
1507    }
1508
1509    /**
1510     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getNestedFormatters(org.opencms.file.CmsObject, org.opencms.file.CmsResource, java.util.Locale, javax.servlet.ServletRequest)
1511     */
1512    public List<String> getNestedFormatters(CmsObject cms, CmsResource res, Locale locale, ServletRequest req) {
1513
1514        List<String> result = new ArrayList<String>();
1515        if (hasNestedFormatters()) {
1516            try {
1517                CmsXmlContent content;
1518                if (req != null) {
1519                    content = CmsXmlContentFactory.unmarshal(cms, res, req);
1520                } else {
1521                    content = CmsXmlContentFactory.unmarshal(cms, cms.readFile(res));
1522                }
1523                Locale matchingLocale = content.getBestMatchingLocale(locale);
1524                if (matchingLocale == null) {
1525                    matchingLocale = content.getLocales().get(0);
1526                }
1527                if (matchingLocale != null) {
1528                    for (String elementPath : m_nestedFormatterElements) {
1529                        List<I_CmsXmlContentValue> values = content.getValues(elementPath, matchingLocale);
1530                        for (I_CmsXmlContentValue value : values) {
1531                            if (value instanceof CmsXmlDisplayFormatterValue) {
1532                                String formatterId = ((CmsXmlDisplayFormatterValue)value).getFormatterId();
1533                                if ((formatterId != null) && !CmsUUID.getNullUUID().toString().equals(formatterId)) {
1534                                    result.add(formatterId);
1535                                }
1536                            } else if (value instanceof CmsXmlVarLinkValue) {
1537                                CmsLink link = ((CmsXmlVarLinkValue)value).getLink(cms);
1538                                CmsUUID formatterId = link.getStructureId();
1539                                if ((formatterId != null) && !formatterId.isNullUUID()) {
1540                                    result.add(formatterId.toString());
1541                                }
1542                            } else if (value instanceof CmsXmlVfsFileValue) {
1543                                CmsLink link = ((CmsXmlVfsFileValue)value).getLink(cms);
1544                                CmsUUID formatterId = link.getStructureId();
1545                                if ((formatterId != null) && !formatterId.isNullUUID()) {
1546                                    result.add(formatterId.toString());
1547                                }
1548                            }
1549                        }
1550                    }
1551                }
1552            } catch (Exception e) {
1553                LOG.error(e.getLocalizedMessage(), e);
1554            }
1555        }
1556        return result;
1557    }
1558
1559    /**
1560     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getParameter(java.lang.String)
1561     */
1562    public String getParameter(String name) {
1563
1564        return m_parameters.get(name);
1565    }
1566
1567    /**
1568     *
1569     * Gets the set of parameters.<p>
1570     *
1571     * @return zhr drz og pstsmrzrtd d
1572     */
1573    public CmsParameterConfiguration getParameters() {
1574
1575        return m_parameters;
1576    }
1577
1578    /**
1579     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getPreview(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, java.lang.String)
1580     */
1581    public String getPreview(CmsObject cms, CmsXmlContent content, String resourcename) {
1582
1583        CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms);
1584        resolver.addMacro(MACRO_PREVIEW_TEMPFILE, resourcename);
1585
1586        return resolver.resolveMacros(m_previewLocation);
1587    }
1588
1589    /**
1590     * @see I_CmsXmlContentHandler#getRelationType(I_CmsXmlContentValue)
1591     */
1592    @Deprecated
1593    public CmsRelationType getRelationType(I_CmsXmlContentValue value) {
1594
1595        if (value == null) {
1596            return CmsRelationType.XML_WEAK;
1597        }
1598        return getRelationType(value.getPath());
1599    }
1600
1601    /**
1602     * @see I_CmsXmlContentHandler#getRelationType(String)
1603     */
1604    public CmsRelationType getRelationType(String xpath) {
1605
1606        return getRelationType(xpath, CmsRelationType.XML_WEAK);
1607    }
1608
1609    /**
1610     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getRelationType(java.lang.String, org.opencms.relations.CmsRelationType)
1611     */
1612    public CmsRelationType getRelationType(String xpath, CmsRelationType defaultType) {
1613
1614        CmsRelationType relationType = null;
1615        if (xpath != null) {
1616
1617            // look up the default from the configured mappings
1618            relationType = m_relations.get(xpath);
1619            if (relationType == null) {
1620                // no value found, try default xpath
1621                String path = CmsXmlUtils.removeAllXpathIndices(xpath);
1622                // look up the default value again without indexes
1623                relationType = m_relations.get(path);
1624            }
1625            if (relationType == null) {
1626                // no value found, try the last simple type path
1627                String path = CmsXmlUtils.getLastXpathElement(xpath);
1628                // look up the default value again for the last simple type
1629                relationType = m_relations.get(path);
1630            }
1631        }
1632        if (relationType == null) {
1633            relationType = defaultType;
1634        }
1635        return relationType;
1636    }
1637
1638    /**
1639     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSearchContentType(org.opencms.xml.types.I_CmsXmlContentValue)
1640     */
1641    public SearchContentType getSearchContentType(I_CmsXmlContentValue value) {
1642
1643        String path = CmsXmlUtils.removeXpath(value.getPath());
1644        // check for name configured in the annotations
1645        SearchContentType searchSetting = m_searchSettings.get(path);
1646        // if no search setting is found within the root handler, move the path upwards to look for other configurations
1647        if (searchSetting == null) {
1648            String[] pathElements = path.split("/");
1649            I_CmsXmlSchemaType type = value.getDocument().getContentDefinition().getSchemaType(pathElements[0]);
1650            for (int i = 1; i < pathElements.length; i++) {
1651                type = ((CmsXmlNestedContentDefinition)type).getNestedContentDefinition().getSchemaType(
1652                    pathElements[i]);
1653                String subPath = getSubPath(pathElements, i);
1654                searchSetting = type.getContentDefinition().getContentHandler().getSearchSettings().get(subPath);
1655                if (searchSetting != null) {
1656                    break;
1657                }
1658            }
1659        }
1660        // if no annotation has been found, use default for value
1661        return (searchSetting == null) ? value.getSearchContentType() : searchSetting;
1662    }
1663
1664    /**
1665     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSearchFields()
1666     */
1667    public Set<CmsSearchField> getSearchFields() {
1668
1669        return Collections.unmodifiableSet(new HashSet<CmsSearchField>(m_searchFields.values()));
1670    }
1671
1672    /**
1673     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSearchFieldsForPage()
1674     */
1675    public Set<CmsSearchField> getSearchFieldsForPage() {
1676
1677        return Collections.unmodifiableSet(new HashSet<CmsSearchField>(m_searchFieldsPage.values()));
1678    }
1679
1680    /**
1681     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSearchSettings()
1682     */
1683    public Map<String, SearchContentType> getSearchSettings() {
1684
1685        return m_searchSettings;
1686    }
1687
1688    /**
1689     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSettings(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
1690     */
1691    public Map<String, CmsXmlContentProperty> getSettings(CmsObject cms, CmsResource resource) {
1692
1693        return Collections.unmodifiableMap(m_settings);
1694    }
1695
1696    /**
1697     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getSynchronizations()
1698     */
1699    public List<String> getSynchronizations() {
1700
1701        return Collections.unmodifiableList(m_synchronizations);
1702    }
1703
1704    /**
1705     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getTabs()
1706     */
1707    public List<CmsXmlContentTab> getTabs() {
1708
1709        return Collections.unmodifiableList(m_tabs);
1710    }
1711
1712    /**
1713     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getTitleMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, java.util.Locale)
1714     */
1715    public String getTitleMapping(CmsObject cms, CmsXmlContent document, Locale locale) {
1716
1717        String result = null;
1718        if (m_titleMappings.size() > 0) {
1719            // a title mapping is available
1720            String xpath = m_titleMappings.get(0);
1721            // currently just use the first mapping found, unsure if multiple "Title" mappings would make sense anyway
1722            result = document.getStringValue(cms, xpath, locale);
1723            if ((result == null)
1724                && (isMappingUsingDefault(xpath, TITLE_PROPERTY_MAPPING)
1725                    || isMappingUsingDefault(xpath, TITLE_PROPERTY_SHARED_MAPPING)
1726                    || isMappingUsingDefault(xpath, TITLE_PROPERTY_INDIVIDUAL_MAPPING))) {
1727                result = getDefault(cms, document.getFile(), null, xpath, locale);
1728            }
1729            if (result != null) {
1730                try {
1731                    CmsGalleryNameMacroResolver resolver = new CmsGalleryNameMacroResolver(
1732                        createRootCms(cms),
1733                        document,
1734                        locale);
1735                    resolver.setKeepEmptyMacros(true);
1736                    result = resolver.resolveMacros(result);
1737                } catch (Exception e) {
1738                    LOG.error(e.getMessage(), e);
1739                }
1740            }
1741        }
1742        return result;
1743    }
1744
1745    /**
1746     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getVersionTransformation()
1747     */
1748    public String getVersionTransformation() {
1749
1750        return m_versionTransformation;
1751    }
1752
1753    /**
1754     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getWidget(org.opencms.file.CmsObject, java.lang.String)
1755     */
1756    public I_CmsWidget getWidget(CmsObject cms, String path) {
1757
1758        String widgetName = m_widgetNames.get(path);
1759        if (widgetName == null) {
1760            return null;
1761        }
1762
1763        // First resolve macros, then try resulting string as widget alias, finally try interpreting it as a class name
1764        if (cms != null) {
1765            CmsMacroResolver resolver = new CmsMacroResolver();
1766            resolver.setCmsObject(cms);
1767            widgetName = resolver.resolveMacros(widgetName);
1768        }
1769        I_CmsWidget result = null;
1770        result = OpenCms.getXmlContentTypeManager().getWidget(widgetName);
1771        if (result != null) {
1772            return result.newInstance();
1773        }
1774        if (CmsStringUtil.isValidJavaClassName(widgetName)) {
1775            try {
1776                Class<?> cls = Class.forName(widgetName, false, getClass().getClassLoader());
1777                if (I_CmsWidget.class.isAssignableFrom(cls)) {
1778                    return (I_CmsWidget)(cls.newInstance());
1779                }
1780            } catch (Exception e) {
1781                LOG.warn(e.getLocalizedMessage(), e);
1782                return null;
1783            }
1784        }
1785        return null;
1786
1787    }
1788
1789    /**
1790     * @see org.opencms.xml.content.I_CmsXmlContentHandler#getWidget(org.opencms.xml.types.I_CmsXmlSchemaType)
1791     */
1792    @Deprecated
1793    public I_CmsWidget getWidget(I_CmsXmlSchemaType value) {
1794
1795        // try the specific widget settings first
1796        I_CmsWidget result = getWidget(null, value.getName());
1797        if (result == null) {
1798            // use default widget mappings
1799            result = OpenCms.getXmlContentTypeManager().getWidgetDefault(value.getTypeName());
1800        } else {
1801            result = result.newInstance();
1802        }
1803        if (result != null) {
1804            // set the configuration value for this widget
1805            String configuration = getConfiguration(value);
1806            if (configuration == null) {
1807                // no individual configuration defined, try to get global default configuration
1808                configuration = OpenCms.getXmlContentTypeManager().getWidgetDefaultConfiguration(result);
1809            }
1810            result.setConfiguration(configuration);
1811        }
1812        return result;
1813    }
1814
1815    /**
1816     * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasModifiableFormatters()
1817     */
1818    public boolean hasModifiableFormatters() {
1819
1820        return (m_formatters != null) && (m_formatters.size() > 0);
1821    }
1822
1823    /**
1824     * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasNestedFormatters()
1825     */
1826    public boolean hasNestedFormatters() {
1827
1828        return !m_nestedFormatterElements.isEmpty();
1829    }
1830
1831    /**
1832     * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasSynchronizedElements()
1833     */
1834    public boolean hasSynchronizedElements() {
1835
1836        return !m_synchronizations.isEmpty();
1837    }
1838
1839    /**
1840     * @see org.opencms.xml.content.I_CmsXmlContentHandler#hasVisibilityHandlers()
1841     */
1842    public boolean hasVisibilityHandlers() {
1843
1844        return (m_visibilityConfigurations != null) && !m_visibilityConfigurations.isEmpty();
1845    }
1846
1847    /**
1848     * @see org.opencms.xml.content.I_CmsXmlContentHandler#initialize(org.dom4j.Element, org.opencms.xml.CmsXmlContentDefinition)
1849     */
1850    public synchronized void initialize(Element appInfoElement, CmsXmlContentDefinition contentDefinition)
1851    throws CmsXmlException {
1852
1853        if (appInfoElement != null) {
1854            // validate the appinfo element XML content with the default appinfo handler schema
1855            validateAppinfoElement(appInfoElement);
1856
1857            // re-initialize the local variables
1858            init();
1859
1860            Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(appInfoElement);
1861            while (i.hasNext()) {
1862                // iterate all elements in the appinfo node
1863                Element element = i.next();
1864                String nodeName = element.getName();
1865                if (nodeName.equals(APPINFO_MAPPINGS)) {
1866                    initMappings(element, contentDefinition);
1867                } else if (nodeName.equals(APPINFO_LAYOUTS)) {
1868                    initLayouts(element, contentDefinition);
1869                } else if (nodeName.equals(APPINFO_VALIDATIONRULES)) {
1870                    initValidationRules(element, contentDefinition);
1871                } else if (nodeName.equals(APPINFO_RELATIONS)) {
1872                    initRelations(element, contentDefinition);
1873                } else if (nodeName.equals(APPINFO_DEFAULTS)) {
1874                    initDefaultValues(element, contentDefinition);
1875                } else if (nodeName.equals(APPINFO_MODELFOLDER)) {
1876                    initModelFolder(element, contentDefinition);
1877                } else if (nodeName.equals(APPINFO_PREVIEW)) {
1878                    initPreview(element, contentDefinition);
1879                } else if (nodeName.equals(APPINFO_RESOURCEBUNDLE)) {
1880                    initResourceBundle(element, contentDefinition, true);
1881                } else if (nodeName.equals(APPINFO_RESOURCEBUNDLES)) {
1882                    initResourceBundle(element, contentDefinition, false);
1883                } else if (nodeName.equals(APPINFO_SEARCHSETTINGS)) {
1884                    initSearchSettings(element, contentDefinition);
1885                } else if (nodeName.equals(APPINFO_TABS)) {
1886                    initTabs(element, contentDefinition);
1887                } else if (nodeName.equals(APPINFO_FORMATTERS)) {
1888                    initFormatters(element, contentDefinition);
1889                } else if (nodeName.equals(APPINFO_HEAD_INCLUDES)) {
1890                    initHeadIncludes(element, contentDefinition);
1891                } else if (nodeName.equals(APPINFO_SETTINGS)) {
1892                    initSettings(element, contentDefinition);
1893                } else if (nodeName.equals(APPINFO_EDIT_HANDLER)) {
1894                    initEditHandler(element);
1895                } else if (nodeName.equals(APPINFO_NESTED_FORMATTERS)) {
1896                    initNestedFormatters(element, contentDefinition);
1897                } else if (nodeName.equals(APPINFO_TEMPLATES)) {
1898                    initTemplates(element, contentDefinition);
1899                } else if (nodeName.equals(APPINFO_DEFAULTWIDGET)) {
1900                    initDefaultWidget(element);
1901                } else if (nodeName.equals(APPINFO_VISIBILITIES)) {
1902                    initVisibilities(element, contentDefinition);
1903                } else if (nodeName.equals(APPINFO_SYNCHRONIZATIONS)) {
1904                    initSynchronizations(element, contentDefinition);
1905                } else if (nodeName.equals(APPINFO_EDITOR_CHANGE_HANDLERS)) {
1906                    initEditorChangeHandlers(element);
1907                } else if (nodeName.equals(APPINFO_MESSAGEKEYHANDLER)) {
1908                    initMessageKeyHandler(element);
1909                } else if (nodeName.equals(APPINFO_PARAMETERS)) {
1910                    initParameters(element);
1911                } else if (nodeName.equals(APPINFO_FIELD_SETTINGS)) {
1912                    initFields(element, contentDefinition);
1913                } else if (nodeName.equals(APPINFO_JSON_RENDERER)) {
1914                    initJsonRenderer(element);
1915                } else if (nodeName.equals(APPINFO_REVERSE_MAPPING_ENABLED)) {
1916                    m_reverseMappingEnabled = Boolean.parseBoolean(element.getTextTrim());
1917                } else if (nodeName.equals(APPINFO_GEOMAPPING)) {
1918                    initGeoMappingEntries(element);
1919                } else if (nodeName.equals(APPINFO_VERSION_TRANSFORMATION)) {
1920                    m_versionTransformation = element.getTextTrim();
1921                }
1922            }
1923        }
1924        m_contentDefinition = contentDefinition;
1925        addGeoMappingField();
1926
1927        // at the end, add default check rules for optional file references
1928        addDefaultCheckRules(contentDefinition, null, null);
1929    }
1930
1931    /**
1932     * @see org.opencms.xml.content.I_CmsXmlContentHandler#invalidateBrokenLinks(CmsObject, CmsXmlContent)
1933     */
1934    public void invalidateBrokenLinks(CmsObject cms, CmsXmlContent document) {
1935
1936        if ((cms == null) || (cms.getRequestContext().getRequestTime() == CmsResource.DATE_RELEASED_EXPIRED_IGNORE)) {
1937            // do not check if the request comes the editor
1938            return;
1939        }
1940        boolean needReinitialization = false;
1941        // iterate the locales
1942        Iterator<Locale> itLocales = document.getLocales().iterator();
1943        while (itLocales.hasNext()) {
1944            Locale locale = itLocales.next();
1945            List<String> removedNodes = new ArrayList<String>();
1946            Map<String, I_CmsXmlContentValue> valuesToRemove = Maps.newHashMap();
1947            // iterate the values
1948            Iterator<I_CmsXmlContentValue> itValues = document.getValues(locale).iterator();
1949            while (itValues.hasNext()) {
1950                I_CmsXmlContentValue value = itValues.next();
1951                InvalidRelationAction invalidRelationAction = getInvalidRelationActionForValue(value);
1952                String path = value.getPath();
1953                // check if this value has already been deleted by parent rules
1954                boolean alreadyRemoved = false;
1955                Iterator<String> itRemNodes = removedNodes.iterator();
1956                while (itRemNodes.hasNext()) {
1957                    String remNode = itRemNodes.next();
1958                    if (path.startsWith(remNode)) {
1959                        alreadyRemoved = true;
1960                        break;
1961                    }
1962                }
1963                // only continue if not already removed and if a rule match
1964                if (alreadyRemoved
1965                    || ((m_relationChecks.get(path) == null)
1966                        && (invalidRelationAction == null)
1967                        && (m_relationChecks.get(CmsXmlUtils.removeXpath(path)) == null))) {
1968                    continue;
1969                }
1970
1971                // check rule matched
1972                if (LOG.isDebugEnabled()) {
1973                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_XMLCONTENT_CHECK_RULE_MATCH_1, path));
1974                }
1975                if (validateLink(cms, value, null)) {
1976                    // invalid link
1977                    if (LOG.isDebugEnabled()) {
1978                        LOG.debug(
1979                            Messages.get().getBundle().key(
1980                                Messages.LOG_XMLCONTENT_CHECK_WARNING_2,
1981                                path,
1982                                value.getStringValue(cms)));
1983                    }
1984                    // find the node to remove
1985                    String parentPath = path;
1986                    boolean firstIteration = true;
1987                    while (isInvalidateParent(parentPath)
1988                        || (firstIteration && (invalidRelationAction == InvalidRelationAction.removeParent))) {
1989                        firstIteration = false;
1990                        // check parent
1991                        parentPath = CmsXmlUtils.removeLastXpathElement(parentPath);
1992                        // log info
1993                        if (LOG.isDebugEnabled()) {
1994                            LOG.debug(
1995                                Messages.get().getBundle().key(
1996                                    Messages.LOG_XMLCONTENT_CHECK_PARENT_2,
1997                                    path,
1998                                    parentPath));
1999                        }
2000                    }
2001                    value = document.getValue(parentPath, locale);
2002                    // Doing the actual DOM modifications here would make the bookmarks for this locale invalid,
2003                    // so we delay it until later because we need the bookmarks for document.getValue() in the next loop iterations
2004                    valuesToRemove.put(parentPath, value);
2005                    // mark node as deleted
2006                    removedNodes.add(parentPath);
2007                }
2008            }
2009            for (I_CmsXmlContentValue valueToRemove : valuesToRemove.values()) {
2010                // detach the value node from the XML document
2011                valueToRemove.getElement().detach();
2012                needReinitialization = true;
2013            }
2014        }
2015        if (needReinitialization) {
2016            document.m_hasInvalidatedBrokenLinks = true;
2017            // re-initialize the XML content
2018            document.initDocument();
2019        }
2020    }
2021
2022    /**
2023     * Returns true if the Acacia editor is disabled for this type.<p>
2024     *
2025     * @return true if the acacia editor is disabled
2026     */
2027    public boolean isAcaciaEditorDisabled() {
2028
2029        return !m_useAcacia;
2030    }
2031
2032    /**
2033     * @see org.opencms.xml.content.I_CmsXmlContentHandler#isContainerPageOnly()
2034     */
2035    public boolean isContainerPageOnly() {
2036
2037        return m_containerPageOnly;
2038    }
2039
2040    /**
2041     * Returns the content field visibilty.<p>
2042     *
2043     * This implementation will be used as default if no other <link>org.opencms.xml.content.I_CmsXmlContentVisibilityHandler</link> is configured.<p>
2044     *
2045     * Only users that are member in one of the specified groups will be allowed to view and edit the given content field.<p>
2046     * The parameter should contain a '|' separated list of group names.<p>
2047     *
2048     * @see org.opencms.xml.content.I_CmsXmlContentVisibilityHandler#isValueVisible(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlSchemaType, java.lang.String, java.lang.String, org.opencms.file.CmsResource, java.util.Locale)
2049     */
2050    public boolean isValueVisible(
2051        CmsObject cms,
2052        I_CmsXmlSchemaType value,
2053        String elementName,
2054        String params,
2055        CmsResource resource,
2056        Locale contentLocale) {
2057
2058        CmsUser user = cms.getRequestContext().getCurrentUser();
2059        boolean result = false;
2060
2061        try {
2062            List<CmsRole> roles = OpenCms.getRoleManager().getRolesOfUser(cms, user.getName(), "", true, false, true);
2063            List<CmsGroup> groups = cms.getGroupsOfUser(user.getName(), false);
2064            CmsMacroResolver resolver = new CmsMacroResolver();
2065            resolver.setCmsObject(cms);
2066            Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
2067            resolver.setMessages(OpenCms.getWorkplaceManager().getMessages(wpLocale));
2068            params = resolver.resolveMacros(params);
2069
2070            if ("visible".equals(params.trim())) {
2071                return true;
2072            }
2073            String[] allowedPrincipals = params.split("\\|");
2074            List<String> groupNames = new ArrayList<String>();
2075            List<String> roleNames = new ArrayList<String>();
2076
2077            for (CmsGroup group : groups) {
2078                groupNames.add(group.getName());
2079            }
2080            for (CmsRole role : roles) {
2081                roleNames.add(role.getRoleName());
2082            }
2083            for (String principal : allowedPrincipals) {
2084                if (CmsRole.hasPrefix(principal)) {
2085                    // prefixed as a role
2086                    principal = CmsRole.removePrefix(principal);
2087                    if (roleNames.contains(principal)) {
2088                        result = true;
2089                        break;
2090                    }
2091                } else {
2092                    // otherwise we always assume this is a group, will work if prefixed or not
2093                    principal = CmsGroup.removePrefix(principal);
2094                    if (groupNames.contains(principal)) {
2095                        result = true;
2096                        break;
2097                    }
2098                }
2099            }
2100        } catch (CmsException e) {
2101            LOG.error(e.getLocalizedMessage(), e);
2102        }
2103
2104        return result;
2105    }
2106
2107    /**
2108     * @see org.opencms.xml.content.I_CmsXmlContentHandler#isVisible(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlSchemaType, java.lang.String, org.opencms.file.CmsResource, java.util.Locale)
2109     */
2110    public boolean isVisible(
2111        CmsObject cms,
2112        I_CmsXmlSchemaType contentValue,
2113        String valuePath,
2114        CmsResource resource,
2115        Locale contentLocale) {
2116
2117        if (hasVisibilityHandlers() && m_visibilityConfigurations.containsKey(valuePath)) {
2118            VisibilityConfiguration config = m_visibilityConfigurations.get(valuePath);
2119            return config.getHandler().isValueVisible(
2120                cms,
2121                contentValue,
2122                valuePath,
2123                config.getParams(),
2124                resource,
2125                contentLocale);
2126        }
2127        return true;
2128
2129    }
2130
2131    /**
2132     * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForUse(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent)
2133     */
2134    public CmsXmlContent prepareForUse(CmsObject cms, CmsXmlContent content) {
2135
2136        // NOOP, just return the unmodified content
2137        return content;
2138    }
2139
2140    /**
2141     * @see org.opencms.xml.content.I_CmsXmlContentHandler#prepareForWrite(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.file.CmsFile)
2142     */
2143    public CmsFile prepareForWrite(CmsObject cms, CmsXmlContent content, CmsFile file) throws CmsException {
2144
2145        if (!content.isAutoCorrectionEnabled()) {
2146            // check if the XML should be corrected automatically (if not already set)
2147            Object attribute = cms.getRequestContext().getAttribute(CmsXmlContent.AUTO_CORRECTION_ATTRIBUTE);
2148            // set the auto correction mode as required
2149            boolean autoCorrectionEnabled = (attribute != null) && ((Boolean)attribute).booleanValue();
2150            content.setAutoCorrectionEnabled(autoCorrectionEnabled);
2151        }
2152        // validate the XML structure before writing the file if required
2153        if (!content.isAutoCorrectionEnabled()) {
2154            // an exception will be thrown if the structure is invalid
2155            content.validateXmlStructure(new CmsXmlEntityResolver(cms));
2156        }
2157        // read the content-conversion property
2158        String contentConversion = CmsHtmlConverter.getConversionSettings(cms, file);
2159        if (CmsStringUtil.isEmptyOrWhitespaceOnly(contentConversion)) {
2160            // enable pretty printing and XHTML conversion of XML content html fields by default
2161            contentConversion = CmsHtmlConverter.PARAM_XHTML;
2162        }
2163        content.setConversion(contentConversion);
2164        // correct the HTML structure
2165        file = content.correctXmlStructure(cms);
2166        content.setFile(file);
2167
2168        // check if any field has a configured attribute mapping
2169        boolean hasAttributeMappings = m_elementMappings.values().stream().flatMap(List::stream).filter(
2170            mapping -> mapping.startsWith(MAPTO_ATTRIBUTE)).findAny().isPresent();
2171
2172        // resolve the file mappings
2173        CmsMappingResolutionContext mappingContext = new CmsMappingResolutionContext(content, hasAttributeMappings);
2174        mappingContext.setCmsObject(cms);
2175        // pass the mapping context as a request context attribute to preserve interface compatibility
2176        cms.getRequestContext().setAttribute(ATTR_MAPPING_RESOLUTION_CONTEXT, mappingContext);
2177        content.resolveMappings(cms);
2178        // ensure all property or permission mappings of deleted optional values are removed
2179        removeEmptyMappings(cms, file, content);
2180        resolveDefaultMappings(cms, file, content);
2181        cms.getRequestContext().removeAttribute(ATTR_MAPPING_RESOLUTION_CONTEXT);
2182        mappingContext.finalizeMappings();
2183        // write categories (if there is a category widget present)
2184        file = writeCategories(cms, file, content);
2185        // return the result
2186        return file;
2187    }
2188
2189    /**
2190     * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.xml.types.I_CmsXmlContentValue)
2191     */
2192    public void resolveMapping(CmsObject cms, CmsXmlContent content, I_CmsXmlContentValue value) throws CmsException {
2193
2194        if (content.getFile() == null) {
2195            throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_RESOLVE_FILE_NOT_FOUND_0));
2196        }
2197
2198        // get the mappings for the element name
2199        boolean valueIsSimple = value.isSimpleType();
2200        String valuePath = value.getPath();
2201        int valueIndex = value.getIndex();
2202        Locale valueLocale = value.getLocale();
2203        CmsObject rootCms1 = createRootCms(cms);
2204        String originalStringValue = null;
2205        if (valueIsSimple) {
2206            originalStringValue = value.getStringValue(rootCms1);
2207        }
2208        resolveMapping(cms, content, valuePath, valueIsSimple, valueIndex, valueLocale, originalStringValue);
2209    }
2210
2211    /**
2212     * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveValidation(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlContentValue, org.opencms.xml.content.CmsXmlContentErrorHandler)
2213     */
2214    public CmsXmlContentErrorHandler resolveValidation(
2215        CmsObject cms,
2216        I_CmsXmlContentValue value,
2217        CmsXmlContentErrorHandler errorHandler) {
2218
2219        if (errorHandler == null) {
2220            // init a new error handler if required
2221            errorHandler = new CmsXmlContentErrorHandler();
2222        }
2223
2224        if (!value.isSimpleType()) {
2225            // no validation for a nested schema is possible
2226            // note that the sub-elements of the nested schema ARE validated by the node visitor,
2227            // it's just the nested schema value itself that does not support validation
2228            return errorHandler;
2229        }
2230
2231        // validate the error rules
2232        errorHandler = validateValue(cms, value, errorHandler, m_validationErrorRules, false);
2233        // validate the warning rules
2234        errorHandler = validateValue(cms, value, errorHandler, m_validationWarningRules, true);
2235        // validate categories
2236        errorHandler = validateCategories(cms, value, errorHandler);
2237        // return the result
2238        return errorHandler;
2239    }
2240
2241    /**
2242     * Adds a check rule for a specified element.<p>
2243     *
2244     * @param contentDefinition the XML content definition this XML content handler belongs to
2245     * @param elementName the element name to add the rule to
2246     * @param invalidate <code>false</code>, to disable link check /
2247     *                   <code>true</code> or <code>node</code>, to invalidate just the single node if the link is broken /
2248     *                   <code>parent</code>, if this rule will invalidate the whole parent node in nested content
2249     * @param type the relation type
2250     *
2251     * @throws CmsXmlException in case an unknown element name is used
2252     */
2253    protected void addCheckRule(
2254        CmsXmlContentDefinition contentDefinition,
2255        String elementName,
2256        String invalidate,
2257        String type)
2258    throws CmsXmlException {
2259
2260        I_CmsXmlSchemaType schemaType = contentDefinition.getSchemaType(elementName);
2261        if (schemaType == null) {
2262            // no element with the given name
2263            throw new CmsXmlException(
2264                Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_INVALID_ELEM_1, elementName));
2265        }
2266        if (!CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType.getTypeName())
2267            && !CmsXmlVarLinkValue.TYPE_NAME.equals(schemaType.getTypeName())) {
2268            // element is not a OpenCmsVfsFile
2269            throw new CmsXmlException(
2270                Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_INVALID_TYPE_1, elementName));
2271        }
2272
2273        // cache the check rule data
2274        Boolean invalidateParent = null;
2275        if ((invalidate == null)
2276            || invalidate.equalsIgnoreCase(Boolean.TRUE.toString())
2277            || invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_NODE)) {
2278            invalidateParent = Boolean.FALSE;
2279        } else if (invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_PARENT)) {
2280            invalidateParent = Boolean.TRUE;
2281        }
2282        if (invalidateParent != null) {
2283            m_relationChecks.put(elementName, invalidateParent);
2284        }
2285        CmsRelationType relationType = (type == null ? CmsRelationType.XML_WEAK : CmsRelationType.valueOfXml(type));
2286        m_relations.put(elementName, relationType);
2287
2288        if (invalidateParent != null) {
2289            // check the whole xpath hierarchy
2290            String path = elementName;
2291            while (CmsStringUtil.isNotEmptyOrWhitespaceOnly(path)) {
2292                if (!isInvalidateParent(path)) {
2293                    // if invalidate type = node, then the node needs to be optional
2294                    if (contentDefinition.getSchemaType(path).getMinOccurs() > 0) {
2295                        // element is not optional
2296                        throw new CmsXmlException(
2297                            Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_NOT_OPTIONAL_1, path));
2298                    }
2299                    // no need to further check
2300                    break;
2301                } else if (!CmsXmlUtils.isDeepXpath(path)) {
2302                    // if invalidate type = parent, then the node needs to be nested
2303                    // document root can not be invalidated
2304                    throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_NOT_EMPTY_DOC_0));
2305                }
2306                path = CmsXmlUtils.removeLastXpathElement(path);
2307            }
2308        }
2309    }
2310
2311    /**
2312     * Adds a configuration value for an element widget.<p>
2313     *
2314     * @param contentDefinition the XML content definition this XML content handler belongs to
2315     * @param elementName the element name
2316     * @param configurationValue the configuration value to use
2317     *
2318     * @throws CmsXmlException in case an unknown element name is used
2319     */
2320    protected void addConfiguration(
2321        CmsXmlContentDefinition contentDefinition,
2322        String elementName,
2323        String configurationValue)
2324    throws CmsXmlException {
2325
2326        if (!elementName.contains("/") && (contentDefinition.getSchemaType(elementName) == null)) {
2327            throw new CmsXmlException(
2328                Messages.get().container(Messages.ERR_XMLCONTENT_CONFIG_ELEM_UNKNOWN_1, elementName));
2329        }
2330
2331        m_configurationValues.put(elementName, configurationValue);
2332    }
2333
2334    /**
2335     * Adds a default value for an element.<p>
2336     *
2337     * @param contentDefinition the XML content definition this XML content handler belongs to
2338     * @param elementName the element name to map
2339     * @param defaultValue the default value to use
2340     * @param resolveMacrosValue the value of the 'resolveMacros' attribute
2341     *
2342     * @throws CmsXmlException in case an unknown element name is used
2343     */
2344    protected void addDefault(
2345        CmsXmlContentDefinition contentDefinition,
2346        String elementName,
2347        String defaultValue,
2348        String resolveMacrosValue)
2349    throws CmsXmlException {
2350
2351        if (contentDefinition.getSchemaType(elementName) == null) {
2352            throw new CmsXmlException(
2353                org.opencms.xml.types.Messages.get().container(
2354                    Messages.ERR_XMLCONTENT_INVALID_ELEM_DEFAULT_1,
2355                    elementName));
2356        }
2357        // store mappings as xpath to allow better control about what is mapped
2358        String xpath = CmsXmlUtils.createXpath(elementName, 1);
2359        m_defaultValues.put(xpath, defaultValue);
2360
2361        // macros are resolved by default
2362        if ((resolveMacrosValue != null) && !Boolean.parseBoolean(resolveMacrosValue)) {
2363            m_nonMacroResolvableDefaults.add(xpath);
2364        }
2365    }
2366
2367    /**
2368     * Adds all needed default check rules recursively for the given schema type.<p>
2369     *
2370     * @param rootContentDefinition the root content definition
2371     * @param schemaType the schema type to check
2372     * @param elementPath the current element path
2373     *
2374     * @throws CmsXmlException if something goes wrong
2375     */
2376    protected void addDefaultCheckRules(
2377        CmsXmlContentDefinition rootContentDefinition,
2378        I_CmsXmlSchemaType schemaType,
2379        String elementPath)
2380    throws CmsXmlException {
2381
2382        if ((schemaType != null) && schemaType.isSimpleType()) {
2383            if ((schemaType.getMinOccurs() == 0)
2384                && (CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType.getTypeName())
2385                    || CmsXmlVarLinkValue.TYPE_NAME.equals(schemaType.getTypeName()))
2386                && !m_relationChecks.containsKey(elementPath)
2387                && !m_relations.containsKey(elementPath)) {
2388                // add default check rule for the element
2389                addCheckRule(rootContentDefinition, elementPath, null, null);
2390            }
2391        } else {
2392            // recursion required
2393            CmsXmlContentDefinition nestedContentDefinition = rootContentDefinition;
2394            if (schemaType != null) {
2395                CmsXmlNestedContentDefinition nestedDefinition = (CmsXmlNestedContentDefinition)schemaType;
2396                nestedContentDefinition = nestedDefinition.getNestedContentDefinition();
2397            }
2398            Iterator<String> itElems = nestedContentDefinition.getSchemaTypes().iterator();
2399            while (itElems.hasNext()) {
2400                String element = itElems.next();
2401                String path = (schemaType != null) ? CmsXmlUtils.concatXpath(elementPath, element) : element;
2402                I_CmsXmlSchemaType nestedSchema = nestedContentDefinition.getSchemaType(element);
2403                if ((schemaType == null) || !nestedSchema.equals(schemaType)) {
2404                    addDefaultCheckRules(rootContentDefinition, nestedSchema, path);
2405                }
2406            }
2407        }
2408    }
2409
2410    /**
2411     * Adds the given element to the compact view set.<p>
2412     *
2413     * @param contentDefinition the XML content definition this XML content handler belongs to
2414     * @param elementName the element name
2415     * @param displayType the display type to use for the element widget
2416     *
2417     * @throws CmsXmlException in case an unknown element name is used
2418     */
2419    protected void addDisplayType(
2420        CmsXmlContentDefinition contentDefinition,
2421        String elementName,
2422        DisplayType displayType)
2423    throws CmsXmlException {
2424
2425        if (contentDefinition.getSchemaType(elementName) == null) {
2426            throw new CmsXmlException(
2427                Messages.get().container(Messages.ERR_XMLCONTENT_CONFIG_ELEM_UNKNOWN_1, elementName));
2428        }
2429        m_displayTypes.put(elementName, displayType);
2430    }
2431
2432    /**
2433     * Finally adds the field used for geo-coordinate mapping by combining the configuration
2434     * from the geomapping section and the field settings.
2435     */
2436    protected void addGeoMappingField() {
2437
2438        CmsGeoMappingConfiguration mappingConfig = getGeoMappingConfiguration();
2439        if (mappingConfig != null) {
2440            CmsSolrField field = new CmsSolrField(
2441                GEOMAPPING_FIELD,
2442                Collections.emptyList(),
2443                CmsLocaleManager.getDefaultLocale(),
2444                "0.000000,0.000000");
2445            I_CmsSearchFieldMapping mapping = new CmsGeoCoordinateFieldMapping(getGeoMappingConfiguration());
2446            field.addMapping(mapping);
2447            m_searchFields.put("__geocoord__", field);
2448        }
2449    }
2450
2451    /**
2452     * Adds an element mapping.<p>
2453     *
2454     * @param contentDefinition the XML content definition this XML content handler belongs to
2455     * @param elementName the element name to map
2456     * @param mapping the mapping to use
2457     * @param useDefault the 'useDefault' attribute
2458     *
2459     * @throws CmsXmlException in case an unknown element name is used
2460     */
2461    protected void addMapping(
2462        CmsXmlContentDefinition contentDefinition,
2463        String elementName,
2464        String mapping,
2465        String useDefault)
2466    throws CmsXmlException {
2467
2468        if (contentDefinition.getSchemaType(elementName) == null) {
2469            throw new CmsXmlException(
2470                Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_MAPPING_1, elementName));
2471        }
2472
2473        // store mappings as xpath to allow better control about what is mapped
2474        String xpath = CmsXmlUtils.createXpath(elementName, 1);
2475        // since 7.0.2 multiple mappings are possible, so the mappings are stored in an array
2476        List<String> values = m_elementMappings.get(xpath);
2477        if (values == null) {
2478            // there should not really be THAT much multiple mappings per value...
2479            values = new ArrayList<String>(4);
2480            m_elementMappings.put(xpath, values);
2481        }
2482        if (Boolean.parseBoolean(useDefault)) {
2483            m_mappingsUsingDefault.add(xpath + ":" + mapping);
2484        }
2485        values.add(mapping);
2486        if (mapping.startsWith(MAPTO_PROPERTY) && mapping.endsWith(":" + CmsPropertyDefinition.PROPERTY_TITLE)) {
2487            // this is a title mapping
2488            m_titleMappings.add(xpath);
2489        }
2490    }
2491
2492    /**
2493     * Adds a nested formatter element.<p>
2494     *
2495     * @param elementName the element name
2496     * @param contentDefinition the content definition
2497     *
2498     * @throws CmsXmlException in case something goes wrong
2499     */
2500    protected void addNestedFormatter(String elementName, CmsXmlContentDefinition contentDefinition)
2501    throws CmsXmlException {
2502
2503        if (contentDefinition.getSchemaType(elementName) == null) {
2504            throw new CmsXmlException(
2505                Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_MAPPING_1, elementName));
2506        }
2507        m_nestedFormatterElements.add(elementName);
2508    }
2509
2510    /**
2511     * Adds a Solr field for an element.<p>
2512     *
2513     * @param contentDefinition the XML content definition this XML content handler belongs to
2514     * @param field the Solr field
2515     */
2516    @Deprecated
2517    protected void addSearchField(CmsXmlContentDefinition contentDefinition, CmsSearchField field) {
2518
2519        addSearchField(contentDefinition, field, I_CmsXmlContentHandler.MappingType.ELEMENT);
2520    }
2521
2522    /**
2523     * Adds a Solr field for an element.<p>
2524     *
2525     * @param contentDefinition the XML content definition this XML content handler belongs to
2526     * @param field the Solr field
2527     * @param type the type, specifying if the field should be attached to the document of the XML content or to all container pages the content is placed on
2528     */
2529    protected void addSearchField(
2530        CmsXmlContentDefinition contentDefinition,
2531        CmsSearchField field,
2532        I_CmsXmlContentHandler.MappingType type) {
2533
2534        Locale locale = null;
2535        if (field instanceof CmsSolrField) {
2536            locale = ((CmsSolrField)field).getLocale();
2537        }
2538        String key = CmsXmlUtils.concatXpath(locale != null ? locale.toString() : null, field.getName());
2539        switch (type) {
2540            case PAGE:
2541                m_searchFieldsPage.put(key, field);
2542                break;
2543            case ELEMENT:
2544            default:
2545                m_searchFields.put(key, field);
2546                break;
2547        }
2548    }
2549
2550    /**
2551     * Adds a search setting for an element.<p>
2552     *
2553     * @param contentDefinition the XML content definition this XML content handler belongs to
2554     * @param elementName the element name to map
2555     * @param value the search setting value to store
2556     *
2557     * @throws CmsXmlException in case an unknown element name is used
2558     */
2559    protected void addSearchSetting(
2560        CmsXmlContentDefinition contentDefinition,
2561        String elementName,
2562        SearchContentType value)
2563    throws CmsXmlException {
2564
2565        if (contentDefinition.getSchemaType(elementName) == null) {
2566            throw new CmsXmlException(
2567                Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_SEARCHSETTINGS_1, elementName));
2568        }
2569        // store the search exclusion as defined
2570        m_searchSettings.put(elementName, value);
2571    }
2572
2573    /**
2574     * Adds search settings as defined by 'simple' syntax in fields.<p>
2575     *
2576     * @param contentDef the content definition
2577     * @param name the element name
2578     * @param value the search setting value
2579     * @throws CmsXmlException if something goes wrong
2580     */
2581    protected void addSimpleSearchSetting(CmsXmlContentDefinition contentDef, String name, String value)
2582    throws CmsXmlException {
2583
2584        SearchContentType searchContentType = SearchContentType.fromString(value);
2585        if (null != searchContentType) {
2586            addSearchSetting(contentDef, name, searchContentType);
2587        } else {
2588            if ("geocoords".equals(value) || "listgeocoords".equals(value)) {
2589                m_primaryGeomappingField = name;
2590                m_searchSettings.put(CmsXmlUtils.removeXpath(name), I_CmsXmlContentValue.SearchContentType.FALSE);
2591            } else {
2592                StringTemplate template = m_searchTemplateGroup.getInstanceOf(value);
2593                if ((template != null) && (template.getFormalArgument("name") != null)) {
2594                    template.setAttribute("name", CmsEncoder.escapeXml(name));
2595                    String xml = template.toString();
2596                    try {
2597                        Document doc = DocumentHelper.parseText(xml);
2598                        initSearchSettings(doc.getRootElement(), contentDef);
2599                    } catch (DocumentException e) {
2600                        LOG.error(e.getLocalizedMessage(), e);
2601                    }
2602                }
2603            }
2604        }
2605    }
2606
2607    /**
2608     * Adds a validation rule for a specified element.<p>
2609     *
2610     * @param contentDefinition the XML content definition this XML content handler belongs to
2611     * @param elementName the element name to add the rule to
2612     * @param regex the validation rule regular expression
2613     * @param message the message in case validation fails (may be null)
2614     * @param isWarning if true, this rule is used for warnings, otherwise it's an error
2615     *
2616     * @throws CmsXmlException in case an unknown element name is used
2617     */
2618    protected void addValidationRule(
2619        CmsXmlContentDefinition contentDefinition,
2620        String elementName,
2621        String regex,
2622        String message,
2623        boolean isWarning)
2624    throws CmsXmlException {
2625
2626        if (contentDefinition.getSchemaType(elementName) == null) {
2627            throw new CmsXmlException(
2628                Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_VALIDATION_1, elementName));
2629        }
2630
2631        if (isWarning) {
2632            m_validationWarningRules.put(elementName, regex);
2633            if (message != null) {
2634                m_validationWarningMessages.put(elementName, message);
2635            }
2636        } else {
2637            m_validationErrorRules.put(elementName, regex);
2638            if (message != null) {
2639                m_validationErrorMessages.put(elementName, message);
2640            }
2641        }
2642    }
2643
2644    /**
2645     * Adds a GUI widget for a specified element.<p>
2646     *
2647     * @param contentDefinition the XML content definition this XML content handler belongs to
2648     * @param elementName the element name to map
2649     * @param name the widget to use as GUI for the element (registered alias or class name)
2650     *
2651     * @throws CmsXmlException in case an unknown element name is used
2652     */
2653    protected void addWidget(CmsXmlContentDefinition contentDefinition, String elementName, String name)
2654    throws CmsXmlException {
2655
2656        if (!elementName.contains("/") && (contentDefinition.getSchemaType(elementName) == null)) {
2657            throw new CmsXmlException(
2658                Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ELEM_LAYOUTWIDGET_1, elementName));
2659        }
2660
2661        if (name.indexOf(I_CmsMacroResolver.MACRO_DELIMITER) == -1) {
2662            // we can only validate this if we don't have macros
2663            if (OpenCms.getXmlContentTypeManager().getWidget(name) == null) {
2664                if (CmsStringUtil.isValidJavaClassName(name)) {
2665                    try {
2666                        Class<?> cls = Class.forName(name, false, getClass().getClassLoader());
2667                        if (!I_CmsWidget.class.isAssignableFrom(cls)
2668                            && !I_CmsComplexWidget.class.isAssignableFrom(cls)) {
2669                            throw new CmsXmlException(
2670                                Messages.get().container(
2671                                    Messages.ERR_XMLCONTENT_INVALID_CUSTOM_CLASS_3,
2672                                    name,
2673                                    elementName,
2674                                    contentDefinition.getSchemaLocation()));
2675
2676                        }
2677                    } catch (Exception e) {
2678                        throw new CmsXmlException(
2679                            Messages.get().container(
2680                                Messages.ERR_XMLCONTENT_INVALID_CUSTOM_CLASS_3,
2681                                name,
2682                                elementName,
2683                                contentDefinition.getSchemaLocation()),
2684                            e);
2685                    }
2686                }
2687            }
2688
2689        }
2690        m_widgetNames.put(elementName, name);
2691    }
2692
2693    /**
2694     * Helper method to create a visibility configuration.<p>
2695     *
2696     * @param className the visibility handler class name
2697     * @param params the parameters for the visibility
2698     *
2699     * @return the visibility configuration
2700     */
2701    protected VisibilityConfiguration createVisibilityConfiguration(String className, String params) {
2702
2703        I_CmsXmlContentVisibilityHandler handler = this;
2704        if (className != null) {
2705            try {
2706                handler = (I_CmsXmlContentVisibilityHandler)(Class.forName(className).newInstance());
2707            } catch (Exception e) {
2708                LOG.error(e.getLocalizedMessage(), e);
2709            }
2710        }
2711        VisibilityConfiguration result = new VisibilityConfiguration(handler, params);
2712        return result;
2713    }
2714
2715    /**
2716     * Returns information about the availability mapping for the given availability attribute.
2717     *
2718     * @param attr the availability attribute
2719     * @return the information about the mapping
2720     */
2721    protected MappingInfo getAttributeMapping(AttributeType attr) {
2722
2723        String target = null;
2724        String source = null;
2725        switch (attr) {
2726            case expiration:
2727                target = MAPTO_ATTRIBUTE + ATTRIBUTE_DATEEXPIRED;
2728                break;
2729
2730            case release:
2731                target = MAPTO_ATTRIBUTE + ATTRIBUTE_DATERELEASED;
2732                break;
2733
2734            default:
2735                break;
2736        }
2737        if (target != null) {
2738            source = getMappingSource(target);
2739        }
2740
2741        return new MappingInfo(source, target);
2742    }
2743
2744    /**
2745     * Returns the configured default locales for the content of the given resource.<p>
2746     *
2747     * @param cms the cms context
2748     * @param resource the resource path to get the default locales for
2749     *
2750     * @return the default locales of the resource
2751     */
2752    protected List<Locale> getLocalesForResource(CmsObject cms, String resource) {
2753
2754        List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(cms, resource);
2755        if ((locales == null) || locales.isEmpty()) {
2756            locales = OpenCms.getLocaleManager().getAvailableLocales();
2757        }
2758        return locales;
2759    }
2760
2761    /**
2762     * Creates editor change handler instances for all nested fields that have configured them in their field settings
2763     *
2764     * @return editor change handlers for all nested fields for which they are configured
2765     */
2766    protected List<I_CmsXmlContentEditorChangeHandler> getNestedEditorChangeHandlers() {
2767
2768        Multimap<String, CmsChangeHandlerConfig> configMap = ArrayListMultimap.create();
2769        collectNestedChangeHandlerConfigs(m_contentDefinition, "", configMap);
2770        List<I_CmsXmlContentEditorChangeHandler> result = new ArrayList<>();
2771        for (String key : configMap.keySet()) {
2772            for (CmsChangeHandlerConfig handlerConfig : configMap.get(key)) {
2773                String path = CmsStringUtil.joinPaths(key, handlerConfig.getField());
2774                path = CmsFileUtil.removeLeadingSeparator(path);
2775                String scope = normalizeChangeHandlerScope(path);
2776                java.util.Optional<I_CmsXmlContentEditorChangeHandler> optHandler = handlerConfig.newHandler(scope);
2777                if (optHandler.isPresent()) {
2778                    result.add(optHandler.get());
2779                }
2780            }
2781        }
2782        List<I_CmsXmlContentEditorChangeHandler> nestedHandlers = result;
2783        return nestedHandlers;
2784    }
2785
2786    /**
2787     * Returns the category reference path for the given value.<p>
2788     *
2789     * @param cms the cms context
2790     * @param value the xml content value
2791     *
2792     * @return the category reference path for the given value
2793     */
2794    protected String getReferencePath(CmsObject cms, I_CmsXmlContentValue value) {
2795
2796        // get the original file instead of the temp file
2797        CmsFile file = value.getDocument().getFile();
2798        String resourceName = cms.getSitePath(file);
2799        if (CmsWorkplace.isTemporaryFile(file)) {
2800            StringBuffer result = new StringBuffer(resourceName.length() + 2);
2801            result.append(CmsResource.getFolderPath(resourceName));
2802            result.append(CmsResource.getName(resourceName).substring(1));
2803            resourceName = result.toString();
2804        }
2805        try {
2806            List<CmsResource> listsib = cms.readSiblings(resourceName, CmsResourceFilter.ALL);
2807            for (int i = 0; i < listsib.size(); i++) {
2808                CmsResource resource = listsib.get(i);
2809                // get the default locale of the resource and set the categories
2810                List<Locale> locales = getLocalesForResource(cms, cms.getSitePath(resource));
2811                for (Locale l : locales) {
2812                    if (value.getLocale().equals(l)) {
2813                        return cms.getSitePath(resource);
2814                    }
2815                }
2816            }
2817        } catch (CmsVfsResourceNotFoundException e) {
2818            // may hapen if editing a new resource
2819            if (LOG.isDebugEnabled()) {
2820                LOG.debug(e.getLocalizedMessage(), e);
2821            }
2822        } catch (CmsException e) {
2823            if (LOG.isErrorEnabled()) {
2824                LOG.error(e.getLocalizedMessage(), e);
2825            }
2826        }
2827        // if the locale can not be found, just take the current file
2828        return cms.getSitePath(file);
2829    }
2830
2831    /**
2832     * Returns the validation message to be displayed if a certain rule was violated.<p>
2833     *
2834     * @param cms the current users OpenCms context
2835     * @param value the value to validate
2836     * @param regex the rule that was violated
2837     * @param valueStr the string value of the given value
2838     * @param matchResult if false, the rule was negated
2839     * @param isWarning if true, this validation indicate a warning, otherwise an error
2840     *
2841     * @return the validation message to be displayed
2842     */
2843    protected String getValidationMessage(
2844        CmsObject cms,
2845        I_CmsXmlContentValue value,
2846        String regex,
2847        String valueStr,
2848        boolean matchResult,
2849        boolean isWarning) {
2850
2851        String message = null;
2852        if (isWarning) {
2853            message = m_validationWarningMessages.get(value.getName());
2854        } else {
2855            message = m_validationErrorMessages.get(value.getName());
2856        }
2857
2858        if (message == null) {
2859            if (isWarning) {
2860                message = MESSAGE_VALIDATION_DEFAULT_WARNING;
2861            } else {
2862                message = MESSAGE_VALIDATION_DEFAULT_ERROR;
2863            }
2864        }
2865
2866        // create additional macro values
2867        Map<String, String> additionalValues = new HashMap<String, String>();
2868        additionalValues.put(CmsMacroResolver.KEY_VALIDATION_VALUE, valueStr);
2869        additionalValues.put(CmsMacroResolver.KEY_VALIDATION_REGEX, ((!matchResult) ? "!" : "") + regex);
2870        additionalValues.put(CmsMacroResolver.KEY_VALIDATION_PATH, value.getPath());
2871
2872        CmsMacroResolver resolver = CmsMacroResolver.newInstance().setCmsObject(cms).setMessages(
2873            getMessages(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms))).setAdditionalMacros(additionalValues);
2874
2875        return resolver.resolveMacros(message);
2876    }
2877
2878    /**
2879     * Called when this content handler is initialized.<p>
2880     */
2881    protected void init() {
2882
2883        m_elementMappings = new HashMap<String, List<String>>();
2884        m_validationErrorRules = new HashMap<String, String>();
2885        m_validationErrorMessages = new HashMap<String, String>();
2886        m_validationWarningRules = new HashMap<String, String>();
2887        m_validationWarningMessages = new HashMap<String, String>();
2888        m_defaultValues = new HashMap<String, String>();
2889        m_configurationValues = new HashMap<String, String>();
2890        m_searchSettings = new HashMap<String, SearchContentType>();
2891        m_relations = new HashMap<String, CmsRelationType>();
2892        m_relationChecks = new HashMap<String, Boolean>();
2893        m_previewLocation = null;
2894        m_modelFolder = null;
2895        m_tabs = new ArrayList<CmsXmlContentTab>();
2896        m_cssHeadIncludes = new LinkedHashSet<String>();
2897        m_jsHeadIncludes = new LinkedHashSet<String>();
2898        m_settings = new LinkedHashMap<String, CmsXmlContentProperty>();
2899        m_titleMappings = new ArrayList<String>(2);
2900        m_formatters = new ArrayList<CmsFormatterBean>();
2901        m_searchFields = new HashMap<String, CmsSearchField>();
2902        m_searchFieldsPage = new HashMap<String, CmsSearchField>();
2903        m_allowedTemplates = new CmsDefaultSet<String>();
2904        m_allowedTemplates.setDefaultMembership(true);
2905        m_displayTypes = new HashMap<String, DisplayType>();
2906        m_synchronizations = new ArrayList<String>();
2907        m_editorChangeHandlers = new ArrayList<I_CmsXmlContentEditorChangeHandler>();
2908        m_nestedFormatterElements = new HashSet<String>();
2909        try (
2910        InputStream stream = CmsDefaultXmlContentHandler.class.getResourceAsStream("simple-searchsetting-configs.st")) {
2911            m_searchTemplateGroup = CmsStringUtil.readStringTemplateGroup(stream);
2912        } catch (IOException e) {
2913            LOG.error(e.getLocalizedMessage(), e);
2914        }
2915    }
2916
2917    /**
2918    * Initializes the default values for this content handler.<p>
2919    *
2920    * Using the default values from the appinfo node, it's possible to have more
2921    * sophisticated logic for generating the defaults then just using the XML schema "default"
2922    * attribute.<p>
2923    *
2924    * @param root the "defaults" element from the appinfo node of the XML content definition
2925    * @param contentDefinition the content definition the default values belong to
2926    * @throws CmsXmlException if something goes wrong
2927    */
2928    protected void initDefaultValues(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
2929
2930        Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_DEFAULT);
2931        while (i.hasNext()) {
2932            // iterate all "default" elements in the "defaults" node
2933            Element element = i.next();
2934            String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
2935            String defaultValue = element.attributeValue(APPINFO_ATTR_VALUE);
2936            String resolveMacrosValue = element.attributeValue(APPINFO_ATTR_RESOLVE_MACROS);
2937            if ((elementName != null) && (defaultValue != null)) {
2938                // add a default value mapping for the element
2939                addDefault(contentDefinition, elementName, defaultValue, resolveMacrosValue);
2940            }
2941        }
2942    }
2943
2944    /**
2945     * Initializes the default complex widget.<p>
2946     *
2947     * @param element the element in which the default complex widget is configured
2948     */
2949    protected void initDefaultWidget(Element element) {
2950
2951        m_defaultWidget = element.attributeValue(APPINFO_ATTR_WIDGET);
2952        m_defaultWidgetConfig = element.attributeValue(APPINFO_ATTR_CONFIGURATION);
2953        try {
2954            m_defaultWidgetInstance = (I_CmsComplexWidget)(Class.forName(m_defaultWidget).newInstance());
2955        } catch (Exception e) {
2956            LOG.error(e.getLocalizedMessage(), e);
2957        }
2958    }
2959
2960    /**
2961     * Initializes the edit handler.<p>
2962     *
2963     * @param handlerElement the edit handler element
2964     */
2965    protected void initEditHandler(Element handlerElement) {
2966
2967        String editHandlerClass = handlerElement.attributeValue(APPINFO_ATTR_CLASS);
2968        Map<String, String> params = Maps.newHashMap();
2969        Element paramsElement = handlerElement.element(APPINFO_PARAMETERS);
2970        if (paramsElement != null) {
2971            for (Element paramElement : paramsElement.elements(APPINFO_PARAM)) {
2972                String name = paramElement.attributeValue(APPINFO_ATTR_NAME);
2973                String value = paramElement.getText();
2974                params.put(name, value);
2975            }
2976        }
2977        try {
2978            m_editHandler = (I_CmsEditHandler)Class.forName(editHandlerClass).newInstance();
2979            m_editHandler.setParameters(params);
2980        } catch (Exception e) {
2981            LOG.error(e.getMessage(), e);
2982        }
2983    }
2984
2985    /**
2986     * Initializes the editor change handlers.<p>
2987     *
2988     * @param element the editorchangehandlers node of the app info
2989     */
2990    protected void initEditorChangeHandlers(Element element) {
2991
2992        Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(element, APPINFO_EDITOR_CHANGE_HANDLER);
2993        while (i.hasNext()) {
2994            // iterate all "default" elements in the "defaults" node
2995            Element handlerElement = i.next();
2996            String handlerClass = handlerElement.attributeValue(APPINFO_ATTR_CLASS);
2997            String configuration = handlerElement.attributeValue(APPINFO_ATTR_CONFIGURATION);
2998            String scope = handlerElement.attributeValue(APPINFO_ATTR_SCOPE);
2999            try {
3000                I_CmsXmlContentEditorChangeHandler handler = (I_CmsXmlContentEditorChangeHandler)Class.forName(
3001                    handlerClass).newInstance();
3002                handler.setConfiguration(configuration);
3003                handler.setScope(scope);
3004                m_editorChangeHandlers.add(handler);
3005            } catch (Exception e) {
3006                LOG.error(e.getLocalizedMessage(), e);
3007            }
3008        }
3009    }
3010
3011    /**
3012     * Processes a single field definition.<p>
3013     *
3014     * @param elem the parent element
3015     * @param contentDef the content definition
3016     *
3017     * @throws CmsXmlException if something goes wrong
3018     */
3019    protected void initField(Element elem, CmsXmlContentDefinition contentDef) throws CmsXmlException {
3020
3021        String nameVal = elem.elementText(CmsConfigurationReader.N_PROPERTY_NAME);
3022        if (nameVal == null) {
3023            throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_BAD_FIELD_NAME_1, nameVal));
3024        }
3025        final String name = nameVal.trim();
3026
3027        String ruleRegex = elem.elementText(CmsConfigurationReader.N_RULE_REGEX);
3028        String ruleType = elem.elementText(CmsConfigurationReader.N_RULE_TYPE);
3029        String error = elem.elementText(CmsConfigurationReader.N_ERROR);
3030        if (error == null) {
3031            error = "";
3032        }
3033        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(ruleRegex)) {
3034            addValidationRule(contentDef, name, ruleRegex, error, "warning".equalsIgnoreCase(ruleType));
3035        }
3036
3037        String defaultValue = elem.elementText(CmsConfigurationReader.N_DEFAULT);
3038        String defaultResolveMacros = elem.elementTextTrim(FieldSettingElems.DefaultResolveMacros.name());
3039        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(defaultValue)) {
3040            addDefault(contentDef, name, defaultValue, defaultResolveMacros);
3041        }
3042
3043        String widget = elem.elementText(CmsConfigurationReader.N_WIDGET);
3044        String widgetConfig = elem.elementText(CmsConfigurationReader.N_WIDGET_CONFIG);
3045        if (widget != null) {
3046            addWidget(contentDef, name, widget);
3047        }
3048        if (widgetConfig != null) {
3049            widgetConfig = widgetConfig.trim();
3050            addConfiguration(contentDef, name, widgetConfig);
3051        }
3052
3053        String niceName = elem.elementText(CmsConfigurationReader.N_DISPLAY_NAME);
3054        if (niceName != null) {
3055            m_fieldNiceNames.put(name, niceName);
3056        }
3057        String description = elem.elementText(CmsConfigurationReader.N_DESCRIPTION);
3058        if (description != null) {
3059            m_fieldDescriptions.put(name, description);
3060        }
3061        for (Element mappingElem : elem.elements(FieldSettingElems.Mapping.name())) {
3062            String mapTo = mappingElem.elementText(FieldSettingElems.MapTo.name());
3063            String useDefault = mappingElem.elementText(FieldSettingElems.UseDefault.name());
3064            if (mapTo != null) {
3065                addMapping(contentDef, name, mapTo, useDefault);
3066            }
3067        }
3068        String display = elem.elementTextTrim(FieldSettingElems.Display.name());
3069        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(display)) {
3070            try {
3071                addDisplayType(contentDef, name, DisplayType.valueOf(display));
3072            } catch (Exception e) {
3073                LOG.error(e.getLocalizedMessage(), e);
3074            }
3075        }
3076        String synchronization = elem.elementTextTrim(FieldSettingElems.Synchronization.name());
3077        if (Boolean.parseBoolean(synchronization)) {
3078            m_synchronizations.add(name);
3079        }
3080        for (Element relElem : elem.elements(FieldSettingElems.Relation.name())) {
3081            String type = relElem.elementTextTrim(FieldSettingElems.Type.name());
3082            String invalidate = relElem.elementTextTrim(FieldSettingElems.Invalidate.name());
3083            if (type != null) {
3084                type = type.toLowerCase();
3085            }
3086            if (invalidate != null) {
3087                invalidate = invalidate.toLowerCase();
3088            }
3089            addCheckRule(contentDef, name, invalidate, type);
3090        }
3091
3092        for (Element visElem : elem.elements(FieldSettingElems.Visibility.name())) {
3093            String params = visElem.getText();
3094            VisibilityConfiguration visConfig = createVisibilityConfiguration(null, params);
3095            m_visibilityConfigurations.put(name, visConfig);
3096        }
3097
3098        for (Element visElem : elem.elements(FieldSettingElems.FieldVisibility.name())) {
3099            String className = visElem.elementTextTrim(FieldSettingElems.Class.name());
3100            String params = visElem.elementTextTrim(FieldSettingElems.Params.name());
3101            VisibilityConfiguration visConfig = createVisibilityConfiguration(className, params);
3102            m_visibilityConfigurations.put(name, visConfig);
3103        }
3104
3105        String nestedFormatter = elem.elementTextTrim(FieldSettingElems.NestedFormatter.name());
3106        if (Boolean.parseBoolean(nestedFormatter)) {
3107            m_nestedFormatterElements.add(name);
3108        }
3109
3110        String search = elem.elementTextTrim(FieldSettingElems.Search.name());
3111        if (search != null) {
3112            addSimpleSearchSetting(contentDef, name, search);
3113        }
3114
3115        String ifInvalidRelationStr = elem.elementTextTrim(FieldSettingElems.IfInvalidRelation.name());
3116        if (CmsStringUtil.isEmptyOrWhitespaceOnly(ifInvalidRelationStr)) {
3117            ifInvalidRelationStr = null;
3118        }
3119        if (ifInvalidRelationStr != null) {
3120            if (name.contains("[") || name.contains("/")) {
3121                LOG.error("Only simple field names allowed for the IfInvalidRelation field setting.");
3122            } else {
3123                try {
3124                    InvalidRelationAction ifInvalidRelation = InvalidRelationAction.valueOf(ifInvalidRelationStr);
3125                    m_invalidRelationActions.put(name, ifInvalidRelation);
3126                } catch (Exception e) {
3127                    LOG.error(e.getLocalizedMessage(), e);
3128                }
3129            }
3130
3131        }
3132
3133        for (Element changeHandlerElem : elem.elements(N_CHANGEHANDLER)) {
3134            String config = changeHandlerElem.attributeValue(A_CONFIGURATION);
3135            String className = changeHandlerElem.getText().trim();
3136            CmsChangeHandlerConfig entry = new CmsChangeHandlerConfig(name, className, config);
3137            m_changeHandlerConfigs.add(entry);
3138
3139        }
3140    }
3141
3142    /**
3143     * Processes all field declarations in the schema.<p>
3144     *
3145     * @param parent the parent element
3146     * @param contentDef the content definition
3147     *
3148     * @throws CmsXmlException if something goes wrong
3149     */
3150    protected void initFields(Element parent, CmsXmlContentDefinition contentDef) throws CmsXmlException {
3151
3152        for (Element fieldElem : parent.elements(N_SETTING)) {
3153            initField(fieldElem, contentDef);
3154        }
3155    }
3156
3157    /**
3158     * Initializes the formatters for this content handler.<p>
3159     *
3160     * @param root the "formatters" element from the appinfo node of the XML content definition
3161     * @param contentDefinition the content definition the formatters belong to
3162     */
3163    protected void initFormatters(Element root, CmsXmlContentDefinition contentDefinition) {
3164
3165        // reading the include resources common for all formatters
3166        Iterator<Element> itFormatter = CmsXmlGenericWrapper.elementIterator(root, APPINFO_FORMATTER);
3167        while (itFormatter.hasNext()) {
3168            // iterate all "formatter" elements in the "formatters" node
3169            Element element = itFormatter.next();
3170            String type = element.attributeValue(APPINFO_ATTR_TYPE);
3171            if (CmsStringUtil.isEmptyOrWhitespaceOnly(type)) {
3172                // if not set use "*" as default for type
3173                type = CmsFormatterBean.WILDCARD_TYPE;
3174            }
3175            String jspRootPath = element.attributeValue(APPINFO_ATTR_URI);
3176            String minWidthStr = element.attributeValue(APPINFO_ATTR_MINWIDTH);
3177            String maxWidthStr = element.attributeValue(APPINFO_ATTR_MAXWIDTH);
3178            String preview = element.attributeValue(APPINFO_ATTR_PREVIEW);
3179            String searchContent = element.attributeValue(APPINFO_ATTR_SEARCHCONTENT);
3180            m_formatters.add(
3181                new CmsFormatterBean(
3182                    type,
3183                    jspRootPath,
3184                    minWidthStr,
3185                    maxWidthStr,
3186                    preview,
3187                    searchContent,
3188                    contentDefinition.getSchemaLocation()));
3189        }
3190    }
3191
3192    /**
3193     * Initializes the head includes for this content handler.<p>
3194     *
3195     * @param root the "headincludes" element from the appinfo node of the XML content definition
3196     * @param contentDefinition the content definition the head-includes belong to
3197     */
3198    protected void initHeadIncludes(Element root, CmsXmlContentDefinition contentDefinition) {
3199
3200        Iterator<Element> itInclude = CmsXmlGenericWrapper.elementIterator(root, APPINFO_HEAD_INCLUDE);
3201        while (itInclude.hasNext()) {
3202            Element element = itInclude.next();
3203            String type = element.attributeValue(APPINFO_ATTR_TYPE);
3204            String uri = element.attributeValue(APPINFO_ATTR_URI);
3205            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(uri)) {
3206                if (ATTRIBUTE_INCLUDE_TYPE_CSS.equals(type)) {
3207                    m_cssHeadIncludes.add(uri);
3208                } else if (ATTRIBUTE_INCLUDE_TYPE_JAVASCRIPT.equals(type)) {
3209                    m_jsHeadIncludes.add(uri);
3210                }
3211            }
3212        }
3213    }
3214
3215    /**
3216     * Reads the JSON renderer settings.
3217     *
3218     * @param element the configuration XML element
3219     */
3220    protected void initJsonRenderer(Element element) {
3221
3222        String cls = element.attributeValue(APPINFO_ATTR_CLASS);
3223        Map<String, String> params = new HashMap<>();
3224        for (Element paramElement : element.elements(APPINFO_PARAM)) {
3225            String name = paramElement.attributeValue(APPINFO_ATTR_NAME);
3226            String value = paramElement.getText();
3227            params.put(name, value);
3228        }
3229        m_jsonRendererSettings = new JsonRendererSettings(cls, params);
3230
3231    }
3232
3233    /**
3234    * Initializes the layout for this content handler.<p>
3235    *
3236    * Unless otherwise instructed, the editor uses one specific GUI widget for each
3237    * XML value schema type. For example, for a {@link org.opencms.xml.types.CmsXmlStringValue}
3238    * the default widget is the {@link org.opencms.widgets.CmsInputWidget}.
3239    * However, certain values can also use more then one widget, for example you may
3240    * also use a {@link org.opencms.widgets.CmsCheckboxWidget} for a String value,
3241    * and as a result the Strings possible values would be either <code>"false"</code> or <code>"true"</code>,
3242    * but nevertheless be a String.<p>
3243    *
3244    * The widget to use can further be controlled using the <code>widget</code> attribute.
3245    * You can specify either a valid widget alias such as <code>StringWidget</code>,
3246    * or the name of a Java class that implements <code>{@link I_CmsWidget}</code>.<p>
3247    *
3248    * Configuration options to the widget can be passed using the <code>configuration</code>
3249    * attribute. You can specify any String as configuration. This String is then passed
3250    * to the widget during initialization. It's up to the individual widget implementation
3251    * to interpret this configuration String.<p>
3252    *
3253    * @param root the "layouts" element from the appinfo node of the XML content definition
3254    * @param contentDefinition the content definition the layout belongs to
3255    *
3256    * @throws CmsXmlException if something goes wrong
3257    */
3258    protected void initLayouts(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
3259
3260        m_useAcacia = safeParseBoolean(root.attributeValue(ATTR_USE_ACACIA), true);
3261        Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_LAYOUT);
3262        while (i.hasNext()) {
3263            // iterate all "layout" elements in the "layouts" node
3264            Element element = i.next();
3265            String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
3266            String widgetClassOrAlias = element.attributeValue(APPINFO_ATTR_WIDGET);
3267            String configuration = element.attributeValue(APPINFO_ATTR_CONFIGURATION);
3268            String displayStr = element.attributeValue(APPINFO_ATTR_DISPLAY);
3269            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(displayStr) && (elementName != null)) {
3270                addDisplayType(contentDefinition, elementName, DisplayType.valueOf(displayStr));
3271            }
3272            if ((elementName != null) && CmsStringUtil.isNotEmptyOrWhitespaceOnly(widgetClassOrAlias)) {
3273                // add a widget mapping for the element
3274                addWidget(contentDefinition, elementName, widgetClassOrAlias);
3275                if (configuration != null) {
3276                    addConfiguration(contentDefinition, elementName, configuration);
3277                }
3278            }
3279        }
3280    }
3281
3282    /**
3283    * Initializes the element mappings for this content handler.<p>
3284    *
3285    * Element mappings allow storing values from the XML content in other locations.
3286    * For example, if you have an element called "Title", it's likely a good idea to
3287    * store the value of this element also in the "Title" property of a XML content resource.<p>
3288    *
3289    * @param root the "mappings" element from the appinfo node of the XML content definition
3290    * @param contentDefinition the content definition the mappings belong to
3291    * @throws CmsXmlException if something goes wrong
3292    */
3293    protected void initMappings(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
3294
3295        Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_MAPPING);
3296        while (i.hasNext()) {
3297            // iterate all "mapping" elements in the "mappings" node
3298            Element element = i.next();
3299            // this is a mapping node
3300            String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
3301            String maptoName = element.attributeValue(APPINFO_ATTR_MAPTO);
3302            String useDefault = element.attributeValue(APPINFO_ATTR_USE_DEFAULT);
3303            if ((elementName != null) && (maptoName != null)) {
3304                // add the element mapping
3305                addMapping(contentDefinition, elementName, maptoName, useDefault);
3306            }
3307        }
3308    }
3309
3310    /**
3311     * Initializes the folder containing the model file(s) for this content handler.<p>
3312     *
3313     * @param root the "modelfolder" element from the appinfo node of the XML content definition
3314     * @param contentDefinition the content definition the model folder belongs to
3315     * @throws CmsXmlException if something goes wrong
3316     */
3317    protected void initModelFolder(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
3318
3319        String master = root.attributeValue(APPINFO_ATTR_URI);
3320        if (master == null) {
3321            throw new CmsXmlException(
3322                Messages.get().container(
3323                    Messages.ERR_XMLCONTENT_MISSING_MODELFOLDER_URI_2,
3324                    root.getName(),
3325                    contentDefinition.getSchemaLocation()));
3326        }
3327        m_modelFolder = master;
3328    }
3329
3330    /**
3331     * Initializes the nested formatter fields.<p>
3332     *
3333     * @param element the formatters element
3334     * @param contentDefinition the content definition
3335     *
3336     * @throws CmsXmlException in case something goes wron
3337     */
3338    protected void initNestedFormatters(Element element, CmsXmlContentDefinition contentDefinition)
3339    throws CmsXmlException {
3340
3341        Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(element, APPINFO_NESTED_FORMATTER);
3342        while (i.hasNext()) {
3343            // iterate all "default" elements in the "defaults" node
3344            Element handlerElement = i.next();
3345            String formatterElement = handlerElement.attributeValue(APPINFO_ATTR_ELEMENT);
3346            addNestedFormatter(formatterElement, contentDefinition);
3347        }
3348    }
3349
3350    /**
3351     * Initializes the parameters from the schema.<p>
3352     *
3353     * @param root the parameter root element
3354     */
3355    protected void initParameters(Element root) {
3356
3357        m_parameters.clear();
3358        for (Element paramElement : root.elements(APPINFO_PARAM)) {
3359            String name = paramElement.attributeValue(APPINFO_ATTR_NAME);
3360            String value = paramElement.getText();
3361            m_parameters.put(name, value);
3362        }
3363
3364    }
3365
3366    /**
3367     * Initializes the preview location for this content handler.<p>
3368     *
3369     * @param root the "preview" element from the appinfo node of the XML content definition
3370     * @param contentDefinition the content definition the validation rules belong to
3371     * @throws CmsXmlException if something goes wrong
3372     */
3373    protected void initPreview(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
3374
3375        String preview = root.attributeValue(APPINFO_ATTR_URI);
3376        if (preview == null) {
3377            throw new CmsXmlException(
3378                Messages.get().container(
3379                    Messages.ERR_XMLCONTENT_MISSING_PREVIEW_URI_2,
3380                    root.getName(),
3381                    contentDefinition.getSchemaLocation()));
3382        }
3383        m_previewLocation = preview;
3384    }
3385
3386    /**
3387     * Initializes the relation configuration for this content handler.<p>
3388     *
3389     * OpenCms performs link checks for all OPTIONAL links defined in XML content values of type
3390     * OpenCmsVfsFile. However, for most projects in the real world a more fine-grained control
3391     * over the link check process is required. For these cases, individual relation behavior can
3392     * be defined for the appinfo node.<p>
3393     *
3394     * Additional here can be defined an optional type for the relations, for instance.<p>
3395     *
3396     * @param root the "relations" element from the appinfo node of the XML content definition
3397     * @param contentDefinition the content definition the check rules belong to
3398     *
3399     * @throws CmsXmlException if something goes wrong
3400     */
3401    protected void initRelations(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
3402
3403        Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_RELATION);
3404        while (i.hasNext()) {
3405            // iterate all "checkrule" elements in the "checkrule" node
3406            Element element = i.next();
3407            String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
3408            String invalidate = element.attributeValue(APPINFO_ATTR_INVALIDATE);
3409            if (invalidate != null) {
3410                invalidate = invalidate.toUpperCase();
3411            }
3412            String type = element.attributeValue(APPINFO_ATTR_TYPE);
3413            if (type != null) {
3414                type = type.toLowerCase();
3415            }
3416            if (elementName != null) {
3417                // add a check rule for the element
3418                addCheckRule(contentDefinition, elementName, invalidate, type);
3419            }
3420        }
3421    }
3422
3423    /**
3424     * Initializes the resource bundle to use for localized messages in this content handler.<p>
3425     *
3426     * @param root the "resourcebundle" element from the appinfo node of the XML content definition
3427     * @param contentDefinition the content definition the validation rules belong to
3428     * @param single if <code>true</code> we process the classic sinle line entry, otherwise it's the multiple line setting
3429     *
3430     * @throws CmsXmlException if something goes wrong
3431     */
3432    protected void initResourceBundle(Element root, CmsXmlContentDefinition contentDefinition, boolean single)
3433    throws CmsXmlException {
3434
3435        if (m_messageBundleNames == null) {
3436            // it's uncommon to have more then one bundle so just initialize an array length of 2
3437            m_messageBundleNames = new ArrayList<String>(2);
3438        }
3439
3440        if (single) {
3441            // single "resourcebundle" node
3442
3443            String messageBundleName = root.attributeValue(APPINFO_ATTR_NAME);
3444            if (messageBundleName == null) {
3445                throw new CmsXmlException(
3446                    Messages.get().container(
3447                        Messages.ERR_XMLCONTENT_MISSING_RESOURCE_BUNDLE_NAME_2,
3448                        root.getName(),
3449                        contentDefinition.getSchemaLocation()));
3450            }
3451            if (!m_messageBundleNames.contains(messageBundleName)) {
3452                // avoid duplicates
3453                m_messageBundleNames.add(messageBundleName);
3454            }
3455            // clear the cached resource bundles for this bundle
3456            CmsResourceBundleLoader.flushBundleCache(messageBundleName, false);
3457
3458        } else {
3459            // multiple "resourcebundles" node
3460
3461            // get an iterator for all "propertybundle" subnodes
3462            Iterator<Element> propertybundles = CmsXmlGenericWrapper.elementIterator(root, APPINFO_PROPERTYBUNDLE);
3463            while (propertybundles.hasNext()) {
3464                // iterate all "propertybundle" elements in the "resourcebundle" node
3465                Element propBundle = propertybundles.next();
3466                String propertyBundleName = propBundle.attributeValue(APPINFO_ATTR_NAME);
3467                if (!m_messageBundleNames.contains(propertyBundleName)) {
3468                    // avoid duplicates
3469                    m_messageBundleNames.add(propertyBundleName);
3470                }
3471                // clear the cached resource bundles for this bundle
3472                CmsResourceBundleLoader.flushBundleCache(propertyBundleName, false);
3473            }
3474
3475            // get an iterator for all "xmlbundle" subnodes
3476            Iterator<Element> xmlbundles = CmsXmlGenericWrapper.elementIterator(root, APPINFO_XMLBUNDLE);
3477            while (xmlbundles.hasNext()) {
3478                Element xmlbundle = xmlbundles.next();
3479                String xmlBundleName = xmlbundle.attributeValue(APPINFO_ATTR_NAME);
3480                // cache the bundle from the XML
3481                if (!m_messageBundleNames.contains(xmlBundleName)) {
3482                    // avoid duplicates
3483                    m_messageBundleNames.add(xmlBundleName);
3484                }
3485                // clear the cached resource bundles for this bundle
3486                CmsResourceBundleLoader.flushBundleCache(xmlBundleName, true);
3487                Iterator<Element> bundles = CmsXmlGenericWrapper.elementIterator(xmlbundle, APPINFO_BUNDLE);
3488                while (bundles.hasNext()) {
3489                    // iterate all "bundle" elements in the "xmlbundle" node
3490                    Element bundle = bundles.next();
3491                    String localeStr = bundle.attributeValue(APPINFO_ATTR_LOCALE);
3492                    Locale locale;
3493                    if (CmsStringUtil.isEmptyOrWhitespaceOnly(localeStr)) {
3494                        // no locale set, so use no locale
3495                        locale = null;
3496                    } else {
3497                        // use provided locale
3498                        locale = CmsLocaleManager.getLocale(localeStr);
3499                    }
3500                    boolean isDefaultLocaleAndNotNull = (locale != null)
3501                        && locale.equals(CmsLocaleManager.getDefaultLocale());
3502
3503                    CmsListResourceBundle xmlBundle = null;
3504
3505                    Iterator<Element> resources = CmsXmlGenericWrapper.elementIterator(bundle, APPINFO_RESOURCE);
3506                    while (resources.hasNext()) {
3507                        // now collect all resource bundle keys
3508                        Element resource = resources.next();
3509                        String key = resource.attributeValue(APPINFO_ATTR_KEY);
3510                        String value = resource.attributeValue(APPINFO_ATTR_VALUE);
3511                        if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
3512                            // read from inside XML tag if value attribute is not set
3513                            value = resource.getTextTrim();
3514                        }
3515                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(key)
3516                            && CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) {
3517                            if (xmlBundle == null) {
3518                                // use lazy initilaizing of the bundle
3519                                xmlBundle = new CmsListResourceBundle();
3520                            }
3521                            xmlBundle.addMessage(key.trim(), value.trim());
3522                        }
3523                    }
3524                    if (xmlBundle != null) {
3525                        CmsResourceBundleLoader.addBundleToCache(xmlBundleName, locale, xmlBundle);
3526                        if (isDefaultLocaleAndNotNull) {
3527                            CmsResourceBundleLoader.addBundleToCache(xmlBundleName, null, xmlBundle);
3528                        }
3529                    }
3530                }
3531            }
3532        }
3533    }
3534
3535    /**
3536     * Initializes the search exclusions values for this content handler.<p>
3537     *
3538     * For the full text search, the value of all elements in one locale of the XML content are combined
3539     * to one big text, which is referred to as the "content" in the context of the full text search.
3540     * With this option, it is possible to hide certain elements from this "content" that does not make sense
3541     * to include in the full text search.<p>
3542     *
3543     * @param root the "searchsettings" element from the appinfo node of the XML content definition
3544     * @param contentDefinition the content definition the default values belong to
3545     *
3546     * @throws CmsXmlException if something goes wrong
3547     */
3548    protected void initSearchSettings(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
3549
3550        String containerPageOnly = root.attributeValue(APPINFO_ATTR_CONTAINER_PAGE_ONLY);
3551        if (!CmsStringUtil.isEmpty(containerPageOnly)) {
3552            m_containerPageOnly = Boolean.valueOf(containerPageOnly).booleanValue();
3553        }
3554        Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_SEARCHSETTING);
3555        while (i.hasNext()) {
3556            Element element = i.next();
3557            String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
3558            String searchContent = element.attributeValue(APPINFO_ATTR_SEARCHCONTENT);
3559            SearchContentType searchContentType = SearchContentType.fromString(searchContent);
3560            if (elementName != null) {
3561                addSearchSetting(contentDefinition, elementName, searchContentType);
3562            }
3563            Iterator<Element> it = CmsXmlGenericWrapper.elementIterator(element, APPINFO_SOLR_FIELD);
3564            Element solrElement;
3565            while (it.hasNext()) {
3566                solrElement = it.next();
3567
3568                String localeNames = solrElement.attributeValue(APPINFO_ATTR_LOCALE);
3569                boolean localized = true;
3570                if ((localeNames != null)
3571                    && (localeNames.equals("none") || localeNames.equals("null") || localeNames.trim().equals(""))) {
3572                    localized = false;
3573                }
3574                List<Locale> locales = OpenCms.getLocaleManager().getAvailableLocales(localeNames);
3575                if (localized && ((locales == null) || locales.isEmpty())) {
3576                    locales = OpenCms.getLocaleManager().getAvailableLocales();
3577                } else if (locales.isEmpty()) {
3578                    locales.add(CmsLocaleManager.getDefaultLocale());
3579                }
3580                for (Locale locale : locales) {
3581                    String targetField = solrElement.attributeValue(APPINFO_ATTR_TARGET_FIELD);
3582                    if (localized) {
3583                        targetField = targetField + "_" + locale.toString();
3584                    }
3585                    String sourceField = solrElement.attributeValue(APPINFO_ATTR_SOURCE_FIELD);
3586                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(sourceField)) {
3587                        int lastUnderScore = sourceField.lastIndexOf("_");
3588                        if (lastUnderScore > 0) {
3589                            sourceField = sourceField.substring(lastUnderScore);
3590                        }
3591                        targetField += sourceField;
3592                    }
3593
3594                    String copyFieldNames = solrElement.attributeValue(APPINFO_ATTR_COPY_FIELDS, "");
3595                    List<String> copyFields = CmsStringUtil.splitAsList(copyFieldNames, ',');
3596                    String defaultValue = solrElement.attributeValue(APPINFO_ATTR_DEFAULT);
3597                    CmsSolrField field = new CmsSolrField(targetField, copyFields, locale, defaultValue);
3598
3599                    // create the field mappings for this element
3600                    Iterator<Element> ite = CmsXmlGenericWrapper.elementIterator(solrElement, APPINFO_ATTR_MAPPING);
3601                    while (ite.hasNext()) {
3602                        Element mappingElement = ite.next();
3603                        field.addMapping(createSearchFieldMapping(contentDefinition, mappingElement, locale));
3604                    }
3605
3606                    // if no mapping was defined yet, create a mapping for the element itself
3607                    if ((field.getMappings() == null) || field.getMappings().isEmpty()) {
3608                        String param = localized ? (locale.toString() + "|" + elementName) : elementName;
3609                        CmsSearchFieldMapping map = new CmsSearchFieldMapping(CmsSearchFieldMappingType.ITEM, param);
3610                        field.addMapping(map);
3611                    }
3612                    Set<I_CmsXmlContentHandler.MappingType> mappingTypes = parseSearchMappingTypes(solrElement);
3613                    for (I_CmsXmlContentHandler.MappingType type : mappingTypes) {
3614                        addSearchField(contentDefinition, field, type);
3615                    }
3616                }
3617            }
3618        }
3619    }
3620
3621    /**
3622     * Initializes the element settings for this content handler.<p>
3623     *
3624     * @param root the "settings" element from the appinfo node of the XML content definition
3625     * @param contentDefinition the content definition the element settings belong to
3626     */
3627    protected void initSettings(Element root, CmsXmlContentDefinition contentDefinition) {
3628
3629        Iterator<Element> itProperties = CmsXmlGenericWrapper.elementIterator(root, APPINFO_SETTING);
3630        while (itProperties.hasNext()) {
3631            Element element = itProperties.next();
3632            CmsXmlContentProperty setting = new CmsXmlContentProperty(
3633                element.attributeValue(APPINFO_ATTR_NAME),
3634                element.attributeValue(APPINFO_ATTR_TYPE),
3635                element.attributeValue(APPINFO_ATTR_WIDGET),
3636                element.attributeValue(APPINFO_ATTR_WIDGET_CONFIG),
3637                element.attributeValue(APPINFO_ATTR_RULE_REGEX),
3638                element.attributeValue(APPINFO_ATTR_RULE_TYPE),
3639                element.attributeValue(APPINFO_ATTR_DEFAULT),
3640                element.attributeValue(APPINFO_ATTR_NICE_NAME),
3641                element.attributeValue(APPINFO_ATTR_DESCRIPTION),
3642                element.attributeValue(APPINFO_ATTR_ERROR),
3643                element.attributeValue(APPINFO_ATTR_PREFERFOLDER));
3644            String name = setting.getName();
3645            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(name)) {
3646                m_settings.put(name, setting);
3647            }
3648        }
3649    }
3650
3651    /**
3652     * Initializes the locale synchronizations elements.<p>
3653     *
3654     * @param root the synchronizations element of the content schema appinfo.
3655     * @param contentDefinition the content definition
3656     */
3657    protected void initSynchronizations(Element root, CmsXmlContentDefinition contentDefinition) {
3658
3659        List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_SYNCHRONIZATION));
3660        for (Element element : elements) {
3661            String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
3662            m_synchronizations.add(elementName);
3663        }
3664    }
3665
3666    /**
3667     * Initializes the tabs for this content handler.<p>
3668     *
3669     * @param root the "tabs" element from the appinfo node of the XML content definition
3670     * @param contentDefinition the content definition the tabs belong to
3671     */
3672    protected void initTabs(Element root, CmsXmlContentDefinition contentDefinition) {
3673
3674        if (Boolean.valueOf(root.attributeValue(APPINFO_ATTR_USEALL, CmsStringUtil.FALSE)).booleanValue()) {
3675            // all first level elements should be treated as tabs
3676            Iterator<I_CmsXmlSchemaType> i = contentDefinition.getTypeSequence().iterator();
3677            while (i.hasNext()) {
3678                // get the type
3679                I_CmsXmlSchemaType type = i.next();
3680                m_tabs.add(new CmsXmlContentTab(type.getName()));
3681            }
3682        } else {
3683            // manual definition of tabs
3684            Iterator<Element> i = CmsXmlGenericWrapper.elementIterator(root, APPINFO_TAB);
3685            while (i.hasNext()) {
3686                // iterate all "tab" elements in the "tabs" node
3687                Element element = i.next();
3688                // this is a tab node
3689                String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
3690                String collapseValue = element.attributeValue(APPINFO_ATTR_COLLAPSE, CmsStringUtil.TRUE);
3691                Node descriptionNode = element.selectSingleNode(APPINFO_ATTR_DESCRIPTION + "/text()");
3692                String description = null;
3693                if (descriptionNode != null) {
3694                    description = descriptionNode.getText();
3695                } else {
3696                    description = element.attributeValue(APPINFO_ATTR_DESCRIPTION);
3697                }
3698
3699                String tabName = element.attributeValue(APPINFO_ATTR_NAME, elementName);
3700                if (elementName != null) {
3701                    // add the element tab
3702                    m_tabs.add(
3703                        new CmsXmlContentTab(
3704                            elementName,
3705                            Boolean.valueOf(collapseValue).booleanValue(),
3706                            tabName,
3707                            description));
3708                }
3709            }
3710            // check if first element has been defined as tab
3711            I_CmsXmlSchemaType type = contentDefinition.getTypeSequence().get(0);
3712            CmsXmlContentTab tab = new CmsXmlContentTab(type.getName());
3713            if (!m_tabs.contains(tab)) {
3714                m_tabs.add(0, tab);
3715            }
3716        }
3717    }
3718
3719    /**
3720     * Initializes the forbidden template contexts.<p>
3721     *
3722     * @param root the root XML element
3723     * @param contentDefinition the content definition
3724     */
3725    protected void initTemplates(Element root, CmsXmlContentDefinition contentDefinition) {
3726
3727        String strEnabledByDefault = root.attributeValue(ATTR_ENABLED_BY_DEFAULT);
3728        m_allowedTemplates.setDefaultMembership(safeParseBoolean(strEnabledByDefault, true));
3729        List<Node> elements = root.selectNodes(APPINFO_TEMPLATE);
3730        for (Node elem : elements) {
3731            boolean enabled = safeParseBoolean(((Element)elem).attributeValue(ATTR_ENABLED), true);
3732            String templateName = elem.getText().trim();
3733            m_allowedTemplates.setContains(templateName, enabled);
3734        }
3735        m_allowedTemplates.freeze();
3736    }
3737
3738    /**
3739     * Initializes the validation rules this content handler.<p>
3740     *
3741     * OpenCms always performs XML schema validation for all XML contents. However,
3742     * for most projects in the real world a more fine-grained control over the validation process is
3743     * required. For these cases, individual validation rules can be defined for the appinfo node.<p>
3744     *
3745     * @param root the "validationrules" element from the appinfo node of the XML content definition
3746     * @param contentDefinition the content definition the validation rules belong to
3747     *
3748     * @throws CmsXmlException if something goes wrong
3749     */
3750    protected void initValidationRules(Element root, CmsXmlContentDefinition contentDefinition) throws CmsXmlException {
3751
3752        List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_RULE));
3753        elements.addAll(CmsXmlGenericWrapper.elements(root, APPINFO_VALIDATIONRULE));
3754        Iterator<Element> i = elements.iterator();
3755        while (i.hasNext()) {
3756            // iterate all "rule" or "validationrule" elements in the "validationrules" node
3757            Element element = i.next();
3758            String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
3759            String regex = element.attributeValue(APPINFO_ATTR_REGEX);
3760            String type = element.attributeValue(APPINFO_ATTR_TYPE);
3761            if (type != null) {
3762                type = type.toLowerCase();
3763            }
3764            String message = element.attributeValue(APPINFO_ATTR_MESSAGE);
3765            if ((elementName != null) && (regex != null)) {
3766                // add a validation rule for the element
3767                addValidationRule(
3768                    contentDefinition,
3769                    elementName,
3770                    regex,
3771                    message,
3772                    APPINFO_ATTR_TYPE_WARNING.equals(type));
3773            }
3774        }
3775    }
3776
3777    /**
3778     * Initializes the content visibility settings.<p>
3779     *
3780     * @param root the visibilities appinfo element
3781     * @param contentDefinition the content definition
3782     */
3783    protected void initVisibilities(Element root, CmsXmlContentDefinition contentDefinition) {
3784
3785        m_visibilityConfigurations = new HashMap<String, VisibilityConfiguration>();
3786        String mainHandlerClassName = root.attributeValue(APPINFO_ATTR_CLASS);
3787        // using self as the default visibility handler implementation
3788        I_CmsXmlContentVisibilityHandler mainHandler = this;
3789        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(mainHandlerClassName)) {
3790            try {
3791                // in case there is a main handler configured, try to instanciate it
3792                Class<?> handlerClass = Class.forName(mainHandlerClassName);
3793                mainHandler = (I_CmsXmlContentVisibilityHandler)handlerClass.newInstance();
3794            } catch (Exception e) {
3795                LOG.error(e.getLocalizedMessage(), e);
3796            }
3797        }
3798        List<Element> elements = new ArrayList<Element>(CmsXmlGenericWrapper.elements(root, APPINFO_VISIBILITY));
3799        for (Element element : elements) {
3800            try {
3801                String elementName = element.attributeValue(APPINFO_ATTR_ELEMENT);
3802                String handlerClassName = element.attributeValue(APPINFO_ATTR_CLASS);
3803                String params = element.attributeValue(APPINFO_ATTR_PARAMS);
3804                I_CmsXmlContentVisibilityHandler handler = null;
3805                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(handlerClassName)) {
3806
3807                    Class<?> handlerClass = Class.forName(handlerClassName);
3808                    handler = (I_CmsXmlContentVisibilityHandler)handlerClass.newInstance();
3809                } else {
3810                    handler = mainHandler;
3811                }
3812                m_visibilityConfigurations.put(elementName, new VisibilityConfiguration(handler, params));
3813
3814            } catch (Exception e) {
3815                LOG.error(e.getLocalizedMessage(), e);
3816            }
3817        }
3818    }
3819
3820    /**
3821     * Returns the is-invalidate-parent flag for the given xpath.<p>
3822     *
3823     * @param xpath the path to get the check rule for
3824     *
3825     * @return the configured is-invalidate-parent flag for the given xpath
3826     */
3827    protected boolean isInvalidateParent(String xpath) {
3828
3829        if (!CmsXmlUtils.isDeepXpath(xpath)) {
3830            return false;
3831        }
3832        Boolean isInvalidateParent = null;
3833        // look up the default from the configured mappings
3834        isInvalidateParent = m_relationChecks.get(xpath);
3835        if (isInvalidateParent == null) {
3836            // no value found, try default xpath
3837            String path = CmsXmlUtils.removeXpath(xpath);
3838            // look up the default value again without indexes
3839            isInvalidateParent = m_relationChecks.get(path);
3840        }
3841        if (isInvalidateParent == null) {
3842            return false;
3843        }
3844        return isInvalidateParent.booleanValue();
3845    }
3846
3847    /**
3848     * Returns the localized resource string for a given message key according to the configured resource bundle
3849     * of this content handler.<p>
3850     *
3851     * If the key was not found in the configured bundle, or no bundle is configured for this
3852     * content handler, the return value is
3853     * <code>"??? " + keyName + " ???"</code>.<p>
3854     *
3855     * @param keyName the key for the desired string
3856     * @param locale the locale to get the key from
3857     *
3858     * @return the resource string for the given key
3859     *
3860     * @see CmsMessages#formatUnknownKey(String)
3861     * @see CmsMessages#isUnknownKey(String)
3862     */
3863    protected String key(String keyName, Locale locale) {
3864
3865        CmsMessages messages = getMessages(locale);
3866        if (messages != null) {
3867            return messages.key(keyName);
3868        }
3869        return CmsMessages.formatUnknownKey(keyName);
3870    }
3871
3872    /**
3873     * @param solrElement the XML node of the &lt;solrfield&gt; node
3874     * @return parsed values of the attribute "addto"
3875     */
3876    protected Set<MappingType> parseSearchMappingTypes(Element solrElement) {
3877
3878        Set<MappingType> result = new HashSet<MappingType>();
3879        String mappingTypes = solrElement.attributeValue(APPINFO_ATTR_ADD_TO);
3880        if (mappingTypes != null) {
3881            String[] types = mappingTypes.split(",");
3882            for (int i = 0; i < types.length; i++) {
3883                String type = types[i].trim();
3884                if (APPINFO_VALUE_ADD_TO_PAGE.equals(type)) {
3885                    result.add(MappingType.PAGE);
3886                } else if (APPINFO_VALUE_ADD_TO_CONTENT.equals(type)) {
3887                    result.add(MappingType.ELEMENT);
3888                }
3889            }
3890        } else {
3891            // for backwards compatibility
3892            result.add(MappingType.ELEMENT);
3893        }
3894
3895        return result;
3896    }
3897
3898    /**
3899     * Removes property values on resources for non-existing, optional elements.<p>
3900     *
3901     * @param cms the current users OpenCms context
3902     * @param file the file which is currently being prepared for writing
3903     * @param content the XML content to remove the property values for
3904     * @throws CmsException in case of read/write errors accessing the OpenCms VFS
3905     */
3906    protected void removeEmptyMappings(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException {
3907
3908        List<CmsResource> siblings = null;
3909        CmsObject rootCms = null;
3910
3911        Iterator<Map.Entry<String, List<String>>> allMappings = m_elementMappings.entrySet().iterator();
3912        while (allMappings.hasNext()) {
3913            Map.Entry<String, List<String>> e = allMappings.next();
3914            String path = e.getKey();
3915            List<String> mappings = e.getValue();
3916            if (mappings == null) {
3917                // nothing to do if we have no mappings at all
3918                continue;
3919            }
3920            if ((siblings == null) || (rootCms == null)) {
3921                // create OpenCms user context initialized with "/" as site root to read all siblings
3922                rootCms = OpenCms.initCmsObject(cms);
3923                rootCms.getRequestContext().setSiteRoot("/");
3924                siblings = rootCms.readSiblings(content.getFile().getRootPath(), CmsResourceFilter.IGNORE_EXPIRATION);
3925            }
3926            for (int v = mappings.size() - 1; v >= 0; v--) {
3927                String mapping = mappings.get(v);
3928
3929                if (mapping.startsWith(MAPTO_PROPERTY_LIST) || mapping.startsWith(MAPTO_PROPERTY)) {
3930                    for (int i = 0; i < siblings.size(); i++) {
3931
3932                        // get siblings filename and locale
3933                        String filename = siblings.get(i).getRootPath();
3934                        Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename);
3935
3936                        if (!content.hasLocale(locale)) {
3937                            // only remove property if the locale fits
3938                            continue;
3939                        }
3940                        if (content.hasValue(path, locale)) {
3941                            // value is available, property must be kept
3942                            continue;
3943                        }
3944
3945                        if (mapping.startsWith(MAPTO_PROPERTY_LIST) || mapping.startsWith(MAPTO_PROPERTY)) {
3946
3947                            String property;
3948                            boolean shared = false;
3949                            if (mapping.startsWith(MAPTO_PROPERTY_LIST_INDIVIDUAL)) {
3950                                property = mapping.substring(MAPTO_PROPERTY_LIST_INDIVIDUAL.length());
3951                            } else if (mapping.startsWith(MAPTO_PROPERTY_LIST_SHARED)) {
3952                                property = mapping.substring(MAPTO_PROPERTY_LIST_SHARED.length());
3953                                shared = true;
3954                            } else if (mapping.startsWith(MAPTO_PROPERTY_LIST)) {
3955                                property = mapping.substring(MAPTO_PROPERTY_LIST.length());
3956                            } else if (mapping.startsWith(MAPTO_PROPERTY_SHARED)) {
3957                                property = mapping.substring(MAPTO_PROPERTY_SHARED.length());
3958                                shared = true;
3959                            } else if (mapping.startsWith(MAPTO_PROPERTY_INDIVIDUAL)) {
3960                                property = mapping.substring(MAPTO_PROPERTY_INDIVIDUAL.length());
3961                            } else {
3962                                property = mapping.substring(MAPTO_PROPERTY.length());
3963                            }
3964                            rootCms.writePropertyObject(
3965                                filename,
3966                                new CmsProperty(
3967                                    property,
3968                                    CmsProperty.DELETE_VALUE,
3969                                    shared ? CmsProperty.DELETE_VALUE : null));
3970                        }
3971                    }
3972                } else if (mapping.startsWith(MAPTO_PERMISSION)) {
3973                    for (int i = 0; i < siblings.size(); i++) {
3974
3975                        // get siblings filename and locale
3976                        String filename = siblings.get(i).getRootPath();
3977                        Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename);
3978
3979                        if (!content.hasLocale(locale)) {
3980                            // only remove property if the locale fits
3981                            continue;
3982                        }
3983                        if (content.hasValue(path, locale)) {
3984                            // value is available, property must be kept
3985                            continue;
3986                        }
3987                        // remove all existing permissions from the file
3988                        List<CmsAccessControlEntry> aces = rootCms.getAccessControlEntries(filename, false);
3989                        for (Iterator<CmsAccessControlEntry> j = aces.iterator(); j.hasNext();) {
3990                            CmsAccessControlEntry ace = j.next();
3991                            if (ace.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID)) {
3992                                // remove the entry "All others", which has to be treated in a special way
3993                                rootCms.rmacc(
3994                                    filename,
3995                                    CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_NAME,
3996                                    CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID.toString());
3997                            } else {
3998                                // this is a group or user principal
3999                                I_CmsPrincipal principal = CmsPrincipal.readPrincipal(rootCms, ace.getPrincipal());
4000                                if (principal.isGroup()) {
4001                                    rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_GROUP, principal.getName());
4002                                } else if (principal.isUser()) {
4003                                    rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_USER, principal.getName());
4004                                }
4005                            }
4006                        }
4007                    }
4008                }
4009            }
4010        }
4011    }
4012
4013    /**
4014     * Resolves those mappings for which no content value exists and useDefault is set to true.<p>
4015     *
4016     * @param cms the CMS context to use
4017     * @param file the content file
4018     * @param content the content object
4019     *
4020     * @throws CmsException if something goes wrong
4021     */
4022    protected void resolveDefaultMappings(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException {
4023
4024        for (Map.Entry<String, List<String>> e : m_elementMappings.entrySet()) {
4025            String path = e.getKey();
4026            List<String> mappings = e.getValue();
4027            if (mappings == null) {
4028                // nothing to do if we have no mappings at all
4029                continue;
4030            }
4031            for (int v = mappings.size() - 1; v >= 0; v--) {
4032                String mapping = mappings.get(v);
4033                if (!isMappingUsingDefault(path, mapping)) {
4034                    continue;
4035                }
4036                for (Locale locale : content.getLocales()) {
4037                    if (content.hasValue(path, locale)) {
4038                        continue;
4039                    } else {
4040                        String defaultValue = getDefault(cms, file, null, path, locale);
4041                        if (defaultValue != null) {
4042                            resolveMapping(cms, content, path, true, 0, locale, defaultValue);
4043                        }
4044                    }
4045                }
4046
4047            }
4048        }
4049    }
4050
4051    /**
4052     * Validates if the given <code>appinfo</code> element node from the XML content definition schema
4053     * is valid according the the capabilities of this content handler.<p>
4054     *
4055     * @param appinfoElement the <code>appinfo</code> element node to validate
4056     *
4057     * @throws CmsXmlException in case the element validation fails
4058     */
4059    protected void validateAppinfoElement(Element appinfoElement) throws CmsXmlException {
4060
4061        // create a document to validate
4062        Document doc = DocumentHelper.createDocument();
4063        Element root = doc.addElement(APPINFO_APPINFO);
4064        // attach the default appinfo schema
4065        root.add(I_CmsXmlSchemaType.XSI_NAMESPACE);
4066        root.addAttribute(I_CmsXmlSchemaType.XSI_NAMESPACE_ATTRIBUTE_NO_SCHEMA_LOCATION, APPINFO_SCHEMA_SYSTEM_ID);
4067        // append the content from the appinfo node in the content definition
4068        root.appendContent(appinfoElement);
4069        // now validate the document with the default appinfo schema
4070        CmsXmlUtils.validateXmlStructure(doc, CmsEncoder.ENCODING_UTF_8, new CmsXmlEntityResolver(null));
4071    }
4072
4073    /**
4074     * The errorHandler parameter is optional, if <code>null</code> is given a new error handler
4075     * instance must be created.<p>
4076     *
4077     * @param cms the current OpenCms user context
4078     * @param value the value to resolve the validation rules for
4079     * @param errorHandler (optional) an error handler instance that contains previous error or warnings
4080     *
4081     * @return an error handler that contains all errors and warnings currently found
4082     */
4083    protected CmsXmlContentErrorHandler validateCategories(
4084        CmsObject cms,
4085        I_CmsXmlContentValue value,
4086        CmsXmlContentErrorHandler errorHandler) {
4087
4088        if (!value.isSimpleType()) {
4089            // do not validate complex types
4090            return errorHandler;
4091        }
4092        I_CmsWidget widget = null;
4093
4094        widget = CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget();
4095        if (!(widget instanceof CmsCategoryWidget)) {
4096            // do not validate widget that are not category widgets
4097            return errorHandler;
4098        }
4099        String stringValue = value.getStringValue(cms);
4100        if (stringValue.isEmpty()) {
4101            return errorHandler;
4102        }
4103        try {
4104            String[] values = stringValue.split(",");
4105            for (int i = 0; i < values.length; i++) {
4106                String val = values[i];
4107                String catPath = CmsCategoryService.getInstance().getCategory(cms, val).getPath();
4108                String refPath = getReferencePath(cms, value);
4109                CmsCategoryService.getInstance().readCategory(cms, catPath, refPath);
4110                if (((CmsCategoryWidget)widget).isOnlyLeafs()) {
4111                    if (!CmsCategoryService.getInstance().readCategories(cms, catPath, false, refPath).isEmpty()) {
4112                        errorHandler.addError(
4113                            value,
4114                            Messages.get().getBundle(value.getLocale()).key(
4115                                Messages.GUI_CATEGORY_CHECK_NOLEAF_ERROR_0));
4116                    }
4117                }
4118            }
4119        } catch (CmsDataAccessException e) {
4120            // expected error in case of empty/invalid value
4121            // see CmsCategory#getCategoryPath(String, String)
4122            if (LOG.isDebugEnabled()) {
4123                LOG.debug(e.getLocalizedMessage(), e);
4124            }
4125            errorHandler.addError(
4126                value,
4127                Messages.get().getBundle(value.getLocale()).key(Messages.GUI_CATEGORY_CHECK_EMPTY_ERROR_0));
4128        } catch (CmsException e) {
4129            // unexpected error
4130            if (LOG.isErrorEnabled()) {
4131                LOG.error(e.getLocalizedMessage(), e);
4132            }
4133            errorHandler.addError(value, e.getLocalizedMessage());
4134        }
4135        return errorHandler;
4136    }
4137
4138    /**
4139     * Validates the given rules against the given value.<p>
4140     *
4141     * @param cms the current users OpenCms context
4142     * @param value the value to validate
4143     * @param errorHandler the error handler to use in case errors or warnings are detected
4144     *
4145     * @return if a broken link has been found
4146     */
4147    protected boolean validateLink(CmsObject cms, I_CmsXmlContentValue value, CmsXmlContentErrorHandler errorHandler) {
4148
4149        // if there is a value of type file reference
4150        if ((value == null) || (!(value instanceof CmsXmlVfsFileValue) && !(value instanceof CmsXmlVarLinkValue))) {
4151            return false;
4152        }
4153        // if the value has a link (this will automatically fix, for instance, the path of moved resources)
4154        CmsLink link = null;
4155        if (value instanceof CmsXmlVfsFileValue) {
4156            link = ((CmsXmlVfsFileValue)value).getLink(cms);
4157        } else if (value instanceof CmsXmlVarLinkValue) {
4158            link = ((CmsXmlVarLinkValue)value).getLink(cms);
4159        }
4160        if ((link == null) || !link.isInternal()) {
4161            return false;
4162        }
4163        try {
4164            String sitePath = cms.getRequestContext().removeSiteRoot(link.getTarget());
4165
4166            // check for links to static resources
4167            if (CmsStaticResourceHandler.isStaticResourceUri(sitePath)) {
4168                return false;
4169            }
4170            // validate the link for error
4171            CmsResource res = null;
4172            CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(link.getTarget());
4173            // the link target may be a root path for a resource in another site
4174            if (site != null) {
4175                CmsObject rootCms = OpenCms.initCmsObject(cms);
4176                rootCms.getRequestContext().setSiteRoot("");
4177                res = rootCms.readResource(link.getTarget(), CmsResourceFilter.IGNORE_EXPIRATION);
4178            } else {
4179                res = cms.readResource(sitePath, CmsResourceFilter.IGNORE_EXPIRATION);
4180            }
4181            // check the time range
4182            if (res != null) {
4183                long time = System.currentTimeMillis();
4184                if (!res.isReleased(time)) {
4185                    if (errorHandler != null) {
4186                        // generate warning message
4187                        errorHandler.addWarning(
4188                            value,
4189                            Messages.get().getBundle(value.getLocale()).key(
4190                                Messages.GUI_XMLCONTENT_CHECK_WARNING_NOT_RELEASED_0));
4191                    }
4192                    return true;
4193                } else if (res.isExpired(time)) {
4194                    if (errorHandler != null) {
4195                        // generate warning message
4196                        errorHandler.addWarning(
4197                            value,
4198                            Messages.get().getBundle(value.getLocale()).key(
4199                                Messages.GUI_XMLCONTENT_CHECK_WARNING_EXPIRED_0));
4200                    }
4201                    return true;
4202                }
4203            }
4204        } catch (CmsException e) {
4205            if (errorHandler != null) {
4206                // generate error message
4207                errorHandler.addError(
4208                    value,
4209                    Messages.get().getBundle(value.getLocale()).key(Messages.GUI_XMLCONTENT_CHECK_ERROR_0));
4210            }
4211            return true;
4212        }
4213        return false;
4214    }
4215
4216    /**
4217     * Validates the given rules against the given value.<p>
4218     *
4219     * @param cms the current users OpenCms context
4220     * @param value the value to validate
4221     * @param errorHandler the error handler to use in case errors or warnings are detected
4222     * @param rules the rules to validate the value against
4223     * @param isWarning if true, this validation should be stored as a warning, otherwise as an error
4224     *
4225     * @return the updated error handler
4226     */
4227    protected CmsXmlContentErrorHandler validateValue(
4228        CmsObject cms,
4229        I_CmsXmlContentValue value,
4230        CmsXmlContentErrorHandler errorHandler,
4231        Map<String, String> rules,
4232        boolean isWarning) {
4233
4234        if (validateLink(cms, value, errorHandler)) {
4235            return errorHandler;
4236        }
4237
4238        if (CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget() instanceof CmsDisplayWidget) {
4239            // display widgets should not be validated
4240            return errorHandler;
4241        }
4242
4243        String valueStr;
4244        try {
4245            valueStr = value.getStringValue(cms);
4246        } catch (Exception e) {
4247            // if the value can not be accessed it's useless to continue
4248            errorHandler.addError(value, e.getMessage());
4249            return errorHandler;
4250        }
4251
4252        String regex = rules.get(value.getName());
4253        if (regex == null) {
4254            // no customized rule, check default XML schema validation rules
4255            return validateValue(cms, value, valueStr, errorHandler, isWarning);
4256        }
4257
4258        boolean matchSign = true;
4259        if (regex.charAt(0) == '!') {
4260            // negate the pattern
4261            matchSign = false;
4262            regex = regex.substring(1);
4263        }
4264
4265        String stringToBeMatched = valueStr;
4266        if (stringToBeMatched == null) {
4267            // set match value to empty String to avoid exceptions in pattern matcher
4268            stringToBeMatched = "";
4269        }
4270
4271        // use the custom validation pattern
4272        final boolean matches;
4273        try {
4274            matches = Pattern.matches(regex, stringToBeMatched);
4275        } catch (PatternSyntaxException | StackOverflowError e) {
4276            final String localizedMessage = (e.getLocalizedMessage() != null ? e.getLocalizedMessage() : "");
4277            final String ticket = String.valueOf(System.currentTimeMillis());
4278
4279            Throwable trace = e;
4280            if (e instanceof StackOverflowError) {
4281                final String stackOverflowInfoMessage = "StackOverflowError thrown on pattern matching during xml"
4282                    + " content validation. (Cause will be also logged in DEBUG level.)\n"
4283                    + "Note 1.- Possible cause: The Java regex engine uses recursive method calls to implement"
4284                    + " backtracking. When a repetition inside a regular expression contains multiple paths"
4285                    + " (i.e. the body of the repetition contains an alternation (|), an optional element or another"
4286                    + " repetition), trying to match the regular expression can cause a stack overflow on large inputs."
4287                    + " This does not happen when using a possessive quantifier (such as *+ instead of *) or when using"
4288                    + " a character class inside a repetition (e.g. [ab]* instead of (a|b)*).\n"
4289                    + "Note 2.- On StackOverflowError, the size of the stacktraces could be limited by the JVM "
4290                    + " and we could be missing information to identify the origin of the problem. To help in this"
4291                    + " case, we create a new exception close to this origin. Alternatively, you can increase"
4292                    + " the depth of the stack trace (for instance, '-XX:MaxJavaStackTraceDepth=1000000') to"
4293                    + " identify it";
4294                trace = LOG.isDebugEnabled()
4295                ? new Exception(stackOverflowInfoMessage, e)
4296                : new Exception(stackOverflowInfoMessage);
4297                errorHandler.addError(
4298                    value,
4299                    Messages.get().getBundle(value.getLocale()).key(
4300                        Messages.GUI_EDITOR_XMLCONTENT_CANNOT_VALIDATE_ERROR_3,
4301                        ticket,
4302                        regex,
4303                        stringToBeMatched));
4304            } else {
4305                errorHandler.addError(
4306                    value,
4307                    Messages.get().getBundle(value.getLocale()).key(
4308                        Messages.GUI_EDITOR_XMLCONTENT_INVALID_RULE_3,
4309                        ticket,
4310                        regex,
4311                        localizedMessage));
4312            }
4313
4314            LOG.warn(
4315                "Ticket "
4316                    + ticket
4317                    + " - "
4318                    + localizedMessage
4319                    + "\n"
4320                    + " Regex='"
4321                    + (matchSign ? "" : "!")
4322                    + regex
4323                    + "'\n"
4324                    + " Path='"
4325                    + value.getPath()
4326                    + "'\n"
4327                    + " Input='"
4328                    + stringToBeMatched
4329                    + "'",
4330                trace);
4331
4332            return errorHandler;
4333        }
4334        if (matchSign != matches) {
4335            // generate the message
4336            String message = getValidationMessage(cms, value, regex, valueStr, matchSign, isWarning);
4337            if (isWarning) {
4338                errorHandler.addWarning(value, message);
4339            } else {
4340                errorHandler.addError(value, message);
4341                // if an error was found, the default XML schema validation is not applied
4342                return errorHandler;
4343            }
4344        }
4345
4346        // no error found, check default XML schema validation rules
4347        return validateValue(cms, value, valueStr, errorHandler, isWarning);
4348    }
4349
4350    /**
4351     * Checks the default XML schema validation rules.<p>
4352     *
4353     * These rules should only be tested if this is not a test for warnings.<p>
4354     *
4355     * @param cms the current users OpenCms context
4356     * @param value the value to validate
4357     * @param valueStr the string value of the given value
4358     * @param errorHandler the error handler to use in case errors or warnings are detected
4359     * @param isWarning if true, this validation should be stored as a warning, otherwise as an error
4360     *
4361     * @return the updated error handler
4362     */
4363    protected CmsXmlContentErrorHandler validateValue(
4364        CmsObject cms,
4365        I_CmsXmlContentValue value,
4366        String valueStr,
4367        CmsXmlContentErrorHandler errorHandler,
4368        boolean isWarning) {
4369
4370        if (isWarning) {
4371            // default schema validation only applies to errors
4372            return errorHandler;
4373        }
4374
4375        String message = null;
4376        if (value instanceof I_CmsXmlValidateWithMessage) {
4377            CmsMessageContainer messageContainer = ((I_CmsXmlValidateWithMessage)value).validateWithMessage(valueStr);
4378            if (null != messageContainer) {
4379                message = messageContainer.key(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms));
4380            }
4381        } else {
4382            if (!value.validateValue(valueStr)) {
4383                // value is not valid, add an error to the handler
4384                message = getValidationMessage(cms, value, value.getTypeName(), valueStr, true, false);
4385            }
4386        }
4387        if (null != message) {
4388            errorHandler.addError(value, message);
4389        }
4390
4391        return errorHandler;
4392    }
4393
4394    /**
4395     * Writes the categories if a category widget is present.<p>
4396     *
4397     * @param cms the cms context
4398     * @param file the file
4399     * @param content the xml content to set the categories for
4400     *
4401     * @return the perhaps modified file
4402     *
4403     * @throws CmsException if something goes wrong
4404     */
4405    protected CmsFile writeCategories(CmsObject cms, CmsFile file, CmsXmlContent content) throws CmsException {
4406
4407        if (CmsWorkplace.isTemporaryFile(file)) {
4408            // ignore temporary file if the original file exists (not the case for direct edit: "new")
4409            if (CmsResource.isTemporaryFileName(file.getRootPath())) {
4410                String originalFileName = CmsResource.getFolderPath(file.getRootPath())
4411                    + CmsResource.getName(file.getRootPath()).substring(CmsResource.TEMP_FILE_PREFIX.length());
4412                if (cms.existsResource(cms.getRequestContext().removeSiteRoot(originalFileName))) {
4413                    // original file exists, ignore it
4414                    return file;
4415                }
4416            } else {
4417                // file name does not start with temporary prefix, ignore the file
4418                return file;
4419            }
4420        }
4421        // check the presence of a category widget
4422        boolean hasCategoryWidget = hasCategoryWidget();
4423        if (!hasCategoryWidget) {
4424            // nothing to do if no category widget is present
4425            return file;
4426        }
4427        boolean modified = false;
4428        // clone the cms object, and use the root site
4429        CmsObject tmpCms = OpenCms.initCmsObject(cms);
4430        tmpCms.getRequestContext().setSiteRoot("");
4431        // read all siblings
4432        try {
4433            List<CmsResource> listsib = tmpCms.readSiblings(file.getRootPath(), CmsResourceFilter.ALL);
4434            for (int i = 0; i < listsib.size(); i++) {
4435                CmsResource resource = listsib.get(i);
4436                // get the default locale of the sibling
4437                List<Locale> locales = getLocalesForResource(tmpCms, resource.getRootPath());
4438                Locale locale = locales.get(0);
4439                for (Locale l : locales) {
4440                    if (content.hasLocale(l)) {
4441                        locale = l;
4442                        break;
4443                    }
4444                }
4445                // remove all previously set categories
4446                boolean clearedCategories = false;
4447                // iterate over all values checking for the category widget
4448                CmsXmlContentWidgetVisitor widgetCollector = new CmsXmlContentWidgetVisitor(cms, locale);
4449                content.visitAllValuesWith(widgetCollector);
4450                Iterator<Map.Entry<String, I_CmsXmlContentValue>> itWidgets = widgetCollector.getValues().entrySet().iterator();
4451                while (itWidgets.hasNext()) {
4452                    Map.Entry<String, I_CmsXmlContentValue> entry = itWidgets.next();
4453                    String xpath = entry.getKey();
4454                    I_CmsWidget widget = widgetCollector.getWidgets().get(xpath);
4455                    I_CmsXmlContentValue value = entry.getValue();
4456                    if (!(widget instanceof CmsCategoryWidget)
4457                        || value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) {
4458                        // ignore other values than categories
4459                        continue;
4460                    }
4461                    if (!clearedCategories) {
4462                        CmsCategoryService.getInstance().clearCategoriesForResource(tmpCms, resource.getRootPath());
4463                        clearedCategories = true;
4464                    }
4465                    String stringValue = value.getStringValue(tmpCms);
4466                    if (CmsStringUtil.isEmptyOrWhitespaceOnly(stringValue)) {
4467                        // skip empty values
4468                        continue;
4469                    }
4470                    try {
4471                        // add the file to the selected category
4472                        String[] catRootPathes = stringValue.split(",");
4473                        for (String catRootPath : catRootPathes) {
4474                            CmsCategory cat = CmsCategoryService.getInstance().getCategory(tmpCms, catRootPath);
4475                            CmsCategoryService.getInstance().addResourceToCategory(
4476                                tmpCms,
4477                                resource.getRootPath(),
4478                                cat.getPath());
4479                        }
4480                    } catch (CmsVfsResourceNotFoundException e) {
4481                        // invalid category
4482                        try {
4483                            // try to remove invalid value
4484                            content.removeValue(value.getName(), value.getLocale(), value.getIndex());
4485                            modified = true;
4486                        } catch (CmsRuntimeException ex) {
4487                            // in case minoccurs prevents removing the invalid value
4488                            if (LOG.isDebugEnabled()) {
4489                                LOG.debug(ex.getLocalizedMessage(), ex);
4490                            }
4491                        }
4492                    }
4493                }
4494            }
4495        } catch (CmsException ex) {
4496            if (LOG.isErrorEnabled()) {
4497                LOG.error(ex.getLocalizedMessage(), ex);
4498            }
4499        }
4500        if (modified) {
4501            // when an invalid category has been removed
4502            file = content.correctXmlStructure(cms);
4503            content.setFile(file);
4504        }
4505        return file;
4506    }
4507
4508    /**
4509     * Creates a search field mapping for the given mapping element and the locale.<p>
4510     *
4511     * @param contentDefinition the content definition
4512     * @param element the mapping element configured in the schema
4513     * @param locale the locale
4514     *
4515     * @return the created search field mapping
4516     *
4517     * @throws CmsXmlException if the dynamic field class could not be found
4518     */
4519    private I_CmsSearchFieldMapping createSearchFieldMapping(
4520        CmsXmlContentDefinition contentDefinition,
4521        Element element,
4522        Locale locale)
4523    throws CmsXmlException {
4524
4525        I_CmsSearchFieldMapping fieldMapping = null;
4526        String typeAsString = element.attributeValue(APPINFO_ATTR_TYPE);
4527        CmsSearchFieldMappingType type = CmsSearchFieldMappingType.valueOf(typeAsString);
4528        switch (type.getMode()) {
4529            case 0: // content
4530            case 3: // item
4531                // localized
4532                String param = locale.toString() + "|" + element.getStringValue();
4533                fieldMapping = new CmsSearchFieldMapping(type, param);
4534                break;
4535            case 1: // property
4536            case 2: // property-search
4537            case 5: // attribute
4538                // not localized
4539                fieldMapping = new CmsSearchFieldMapping(type, element.getStringValue());
4540                break;
4541            case 4: // dynamic
4542                String mappingClass = element.attributeValue(APPINFO_ATTR_CLASS);
4543                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(mappingClass)) {
4544                    try {
4545                        fieldMapping = (I_CmsSearchFieldMapping)Class.forName(mappingClass).newInstance();
4546                        fieldMapping.setType(CmsSearchFieldMappingType.DYNAMIC);
4547                        fieldMapping.setParam(element.getStringValue());
4548                        fieldMapping.setLocale(locale);
4549                    } catch (Exception e) {
4550                        throw new CmsXmlException(
4551                            Messages.get().container(
4552                                Messages.ERR_XML_SCHEMA_MAPPING_CLASS_NOT_EXIST_3,
4553                                mappingClass,
4554                                contentDefinition.getTypeName(),
4555                                contentDefinition.getSchemaLocation()));
4556                    }
4557
4558                }
4559                break;
4560            default:
4561                // NOOP
4562        }
4563        if (fieldMapping != null) {
4564            fieldMapping.setDefaultValue(element.attributeValue(APPINFO_ATTR_DEFAULT));
4565        }
4566        return fieldMapping;
4567    }
4568
4569    /**
4570     * Gets the xpath mapped to a given target, if the mapping exists, and null otherwise.
4571     *
4572     * @param target the mapping target
4573     * @return the xpath mapped to the target
4574     */
4575    private String getMappingSource(String target) {
4576
4577        for (Map.Entry<String, List<String>> entry : m_elementMappings.entrySet()) {
4578            if (entry.getValue().contains(target)) {
4579                return entry.getKey();
4580            }
4581        }
4582        return null;
4583    }
4584
4585    /**
4586     * Utility method to return a path fragment.<p>
4587     *
4588     * @param pathElements the path elements
4589     * @param begin the begin index
4590     *
4591     * @return the path
4592     */
4593    private String getSubPath(String[] pathElements, int begin) {
4594
4595        String result = "";
4596        for (int i = begin; i < pathElements.length; i++) {
4597            result += pathElements[i] + "/";
4598        }
4599        if (result.length() > 0) {
4600            result = result.substring(0, result.length() - 1);
4601        }
4602        return result;
4603    }
4604
4605    /**
4606     * Checks if any configured value type is an OpenCmsCategory.
4607     *
4608     * @return true if any configured value type is an OpenCmsCategory
4609     */
4610    private boolean hasCategoryType() {
4611
4612        try {
4613            for (I_CmsXmlSchemaType typeEntry : m_contentDefinition.getTypeSequence()) {
4614                String typeName = typeEntry.getTypeName();
4615                I_CmsXmlSchemaType type = OpenCms.getXmlContentTypeManager().getContentType(typeName);
4616                if (type instanceof CmsXmlCategoryValue) {
4617                    return true;
4618                }
4619            }
4620        } catch (Exception e) {
4621            LOG.debug(e.getLocalizedMessage(), e);
4622        }
4623        return false;
4624
4625    }
4626
4627    /**
4628     * Checks whether a category widget is configured.
4629     *
4630     * @return true if a category widget is configured
4631     */
4632    private boolean hasCategoryWidget() {
4633
4634        if (m_hasCategoryWidget == null) {
4635            boolean result = false;
4636            for (Map.Entry<String, String> widgetEntry : m_widgetNames.entrySet()) {
4637                String widgetName = widgetEntry.getValue();
4638                I_CmsWidget widget = OpenCms.getXmlContentTypeManager().getWidget(widgetName);
4639                if ((widget != null) && (widget instanceof CmsCategoryWidget)) {
4640                    result = true;
4641                    break;
4642                }
4643            }
4644            result = result || hasCategoryType();
4645            m_hasCategoryWidget = Boolean.valueOf(result);
4646            return result;
4647        }
4648        return m_hasCategoryWidget.booleanValue();
4649
4650    }
4651
4652    /**
4653     * Initializes the geo-mapping configuration.
4654     *
4655     * @param element the configuration node
4656     */
4657    private void initGeoMappingEntries(Element element) {
4658
4659        try {
4660            for (Element child : element.elements()) {
4661                EntryType type = EntryType.valueOf(child.getName());
4662                String value = child.getText();
4663                Entry entry = new Entry(type, value.trim());
4664                m_geomappingEntries.add(entry);
4665            }
4666        } catch (Exception e) {
4667            LOG.error(e.getLocalizedMessage(), e);
4668        }
4669    }
4670
4671    /**
4672     * Initializes the message key fall back handler.<p>
4673     *
4674     * @param element the XML element node
4675     */
4676    private void initMessageKeyHandler(Element element) {
4677
4678        String className = element.attributeValue(APPINFO_ATTR_CLASS);
4679        String configuration = element.attributeValue(APPINFO_ATTR_CONFIGURATION);
4680        try {
4681            Object messageKeyHandler = Class.forName(className).getConstructor(String.class).newInstance(configuration);
4682            m_messageKeyHandler = (CmsMultiMessages.I_KeyFallbackHandler)messageKeyHandler;
4683        } catch (Exception e) {
4684            LOG.error(e.getLocalizedMessage(), e);
4685        }
4686    }
4687
4688    /**
4689     * Checks if the given mapping has the 'useDefault' flag set to true.<p>
4690     *
4691     * @param path the mapping path
4692     * @param mapping the mapping type
4693     *
4694     * @return true if 'useDefault' is enabled for this mapping
4695     */
4696    private boolean isMappingUsingDefault(String path, String mapping) {
4697
4698        String key = path + ":" + mapping;
4699        return m_mappingsUsingDefault.contains(key);
4700    }
4701
4702    /**
4703     * Helper method which does most of the mapping resolution work.<p>
4704     *
4705     * @param cms the CMS context to use
4706     * @param content the content object
4707     * @param valuePath the xpath of the value
4708     * @param valueIsSimple true if this is a simple value
4709     * @param valueIndex the index of the value
4710     * @param valueLocale the locale of the value
4711     * @param originalStringValue the value as a string
4712     *
4713     * @throws CmsException if something goes wrong
4714     */
4715    private void resolveMapping(
4716        CmsObject cms,
4717        CmsXmlContent content,
4718        String valuePath,
4719        boolean valueIsSimple,
4720        int valueIndex,
4721        Locale valueLocale,
4722        String originalStringValue)
4723    throws CmsException {
4724
4725        CmsObject rootCms = createRootCms(cms);
4726        // get the original VFS file from the content
4727        CmsFile file = content.getFile();
4728        if (!valueIsSimple) {
4729            // no mappings for a nested schema are possible
4730            // note that the sub-elements of the nested schema ARE mapped by the node visitor,
4731            // it's just the nested schema value itself that does not support mapping
4732            return;
4733        }
4734
4735        List<String> mappings = getMappings(valuePath);
4736        if (mappings.size() == 0) {
4737            // nothing to do if we have no mappings at all
4738            return;
4739        }
4740        // create OpenCms user context initialized with "/" as site root to read all siblings
4741        // read all siblings of the file
4742        List<CmsResource> siblings = rootCms.readSiblings(
4743            content.getFile().getRootPath(),
4744            CmsResourceFilter.IGNORE_EXPIRATION);
4745
4746        Set<CmsResource> urlNameMappingResources = new HashSet<CmsResource>();
4747        boolean mapToUrlName = false;
4748        urlNameMappingResources.add(content.getFile());
4749        // since 7.0.2 multiple mappings are possible
4750
4751        // get the string value of the current node
4752
4753        CmsGalleryNameMacroResolver resolver = new CmsGalleryNameMacroResolver(rootCms, content, valueLocale);
4754        resolver.setKeepEmptyMacros(true);
4755        String stringValue = resolver.resolveMacros(originalStringValue);
4756        CmsMappingResolutionContext mappingContext = (CmsMappingResolutionContext)(cms.getRequestContext().getAttribute(
4757            ATTR_MAPPING_RESOLUTION_CONTEXT));
4758
4759        for (String mapping : mappings) {
4760
4761            if (CmsStringUtil.isNotEmpty(mapping)) {
4762
4763                // attribute mapping now does its own handling of siblings/locales in CmsMappingResolutionContext,
4764                // so we just save the mapped release/expiration dates for later, and we do this before the sibling/locale handling
4765                // logic in this method.
4766                if (mapping.startsWith(MAPTO_ATTRIBUTE)) {
4767
4768                    // this is an attribute mapping
4769                    String attribute = mapping.substring(MAPTO_ATTRIBUTE.length());
4770                    switch (ATTRIBUTES.indexOf(attribute)) {
4771                        case 0: // date released
4772                            long date = 0;
4773                            try {
4774                                date = Long.valueOf(stringValue).longValue();
4775                            } catch (NumberFormatException e) {
4776                                // ignore, value can be a macro
4777                            }
4778                            if (date == 0) {
4779                                date = CmsResource.DATE_RELEASED_DEFAULT;
4780                            }
4781                            mappingContext.putReleaseDate(valueLocale, date);
4782                            break;
4783                        case 1: // date expired
4784                            date = 0;
4785                            try {
4786                                date = Long.valueOf(stringValue).longValue();
4787                            } catch (NumberFormatException e) {
4788                                // ignore, value can be a macro
4789                            }
4790                            if (date == 0) {
4791                                date = CmsResource.DATE_EXPIRED_DEFAULT;
4792                            }
4793                            mappingContext.putExpirationDate(valueLocale, date);
4794                            break;
4795                        default:
4796                            // ignore invalid / other mappings
4797                    }
4798                    continue; // skip to next mapping
4799                }
4800
4801                // for multiple language mappings, we need to ensure
4802                // a) all siblings are handled
4803                // b) only the "right" locale is mapped to a sibling
4804                for (int i = (siblings.size() - 1); i >= 0; i--) {
4805                    // get filename
4806                    String filename = (siblings.get(i)).getRootPath();
4807                    if (mapping.startsWith(MAPTO_URLNAME)) {
4808                        // should be written regardless of whether there is a sibling with the correct locale
4809                        mapToUrlName = true;
4810                    }
4811
4812                    Locale locale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename);
4813                    if (!locale.equals(valueLocale)) {
4814                        // only map property if the locale fits
4815                        continue;
4816                    }
4817
4818                    // make sure the file is locked
4819                    CmsLock lock = rootCms.getLock(filename);
4820                    if (lock.isUnlocked()) {
4821                        rootCms.lockResource(filename);
4822                    } else if (!lock.isDirectlyOwnedInProjectBy(rootCms)) {
4823                        rootCms.changeLock(filename);
4824                    }
4825
4826                    if (mapping.startsWith(MAPTO_PERMISSION) && (valueIndex == 0)) {
4827
4828                        // map value to a permission
4829                        // example of a mapping: mapto="permission:GROUP:+r+v|GROUP.ALL_OTHERS:|GROUP.Projectmanagers:+r+v+w+c"
4830
4831                        // get permission(s) to set
4832                        String permissionMappings = mapping.substring(MAPTO_PERMISSION.length());
4833                        String mainMapping = permissionMappings;
4834                        Map<String, String> permissionsToSet = new HashMap<String, String>();
4835
4836                        // separate permission to set for element value from other permissions to set
4837                        int sepIndex = permissionMappings.indexOf('|');
4838                        if (sepIndex != -1) {
4839                            mainMapping = permissionMappings.substring(0, sepIndex);
4840                            permissionMappings = permissionMappings.substring(sepIndex + 1);
4841                            permissionsToSet = CmsStringUtil.splitAsMap(permissionMappings, "|", ":");
4842                        }
4843
4844                        // determine principal type and permission string to set
4845                        String principalType = I_CmsPrincipal.PRINCIPAL_GROUP;
4846                        String permissionString = mainMapping;
4847                        sepIndex = mainMapping.indexOf(':');
4848                        if (sepIndex != -1) {
4849                            principalType = mainMapping.substring(0, sepIndex);
4850                            permissionString = mainMapping.substring(sepIndex + 1);
4851                        }
4852                        if (permissionString.toLowerCase().indexOf('o') == -1) {
4853                            permissionString += "+o";
4854                        }
4855
4856                        // remove all existing permissions from the file
4857                        List<CmsAccessControlEntry> aces = rootCms.getAccessControlEntries(filename, false);
4858                        for (Iterator<CmsAccessControlEntry> j = aces.iterator(); j.hasNext();) {
4859                            CmsAccessControlEntry ace = j.next();
4860                            if (ace.getPrincipal().equals(CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID)) {
4861                                // remove the entry "All others", which has to be treated in a special way
4862                                rootCms.rmacc(
4863                                    filename,
4864                                    CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_NAME,
4865                                    CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_ID.toString());
4866                            } else {
4867                                // this is a group or user principal
4868                                I_CmsPrincipal principal = CmsPrincipal.readPrincipal(rootCms, ace.getPrincipal());
4869                                if (principal.isGroup()) {
4870                                    rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_GROUP, principal.getName());
4871                                } else if (principal.isUser()) {
4872                                    rootCms.rmacc(filename, I_CmsPrincipal.PRINCIPAL_USER, principal.getName());
4873                                }
4874                            }
4875                        }
4876
4877                        // set additional permissions that are defined in mapping
4878                        for (Iterator<Map.Entry<String, String>> j = permissionsToSet.entrySet().iterator(); j.hasNext();) {
4879                            Map.Entry<String, String> entry = j.next();
4880                            sepIndex = entry.getKey().indexOf('.');
4881                            if (sepIndex != -1) {
4882                                String type = entry.getKey().substring(0, sepIndex);
4883                                String name = entry.getKey().substring(sepIndex + 1);
4884                                String permissions = entry.getValue();
4885                                if (permissions.toLowerCase().indexOf('o') == -1) {
4886                                    permissions += "+o";
4887                                }
4888                                try {
4889                                    rootCms.chacc(filename, type, name, permissions);
4890                                } catch (CmsException e) {
4891                                    // setting permission did not work
4892                                    LOG.error(e.getLocalizedMessage(), e);
4893                                }
4894                            }
4895                        }
4896
4897                        // set permission(s) using the element value(s)
4898                        // the set with all selected principals
4899                        TreeSet<String> allPrincipals = new TreeSet<String>();
4900                        String path = CmsXmlUtils.removeXpathIndex(valuePath);
4901                        List<I_CmsXmlContentValue> values = content.getValues(path, valueLocale);
4902                        Iterator<I_CmsXmlContentValue> j = values.iterator();
4903                        while (j.hasNext()) {
4904                            I_CmsXmlContentValue val = j.next();
4905                            String principalName = val.getStringValue(rootCms);
4906                            // the prinicipal name can be a principal list
4907                            List<String> principalNames = CmsStringUtil.splitAsList(
4908                                principalName,
4909                                PRINCIPAL_LIST_SEPARATOR);
4910                            // iterate over the principals
4911                            Iterator<String> iterPrincipals = principalNames.iterator();
4912                            while (iterPrincipals.hasNext()) {
4913                                // get the next principal
4914                                String principal = iterPrincipals.next();
4915                                allPrincipals.add(principal);
4916                            }
4917                        }
4918                        // iterate over the set with all principals and set the permissions
4919                        Iterator<String> iterAllPricinipals = allPrincipals.iterator();
4920                        while (iterAllPricinipals.hasNext()) {
4921                            // get the next principal
4922                            String principal = iterAllPricinipals.next();
4923                            rootCms.chacc(filename, principalType, principal, permissionString);
4924                        }
4925                        // special case: permissions are written only to one sibling, end loop
4926                        i = 0;
4927                    } else if (mapping.startsWith(MAPTO_PROPERTY_LIST) && (valueIndex == 0)) {
4928
4929                        boolean mapToShared;
4930                        int prefixLength;
4931                        // check which mapping is used (shared or individual)
4932                        if (mapping.startsWith(MAPTO_PROPERTY_LIST_SHARED)) {
4933                            mapToShared = true;
4934                            prefixLength = MAPTO_PROPERTY_LIST_SHARED.length();
4935                        } else if (mapping.startsWith(MAPTO_PROPERTY_LIST_INDIVIDUAL)) {
4936                            mapToShared = false;
4937                            prefixLength = MAPTO_PROPERTY_LIST_INDIVIDUAL.length();
4938                        } else {
4939                            mapToShared = false;
4940                            prefixLength = MAPTO_PROPERTY_LIST.length();
4941                        }
4942
4943                        // this is a property list mapping
4944                        String property = mapping.substring(prefixLength);
4945
4946                        String path = CmsXmlUtils.removeXpathIndex(valuePath);
4947                        List<I_CmsXmlContentValue> values = content.getValues(path, valueLocale);
4948                        Iterator<I_CmsXmlContentValue> j = values.iterator();
4949                        StringBuffer result = new StringBuffer(values.size() * 64);
4950                        while (j.hasNext()) {
4951                            I_CmsXmlContentValue val = j.next();
4952                            result.append(val.getStringValue(rootCms));
4953                            if (j.hasNext()) {
4954                                result.append(CmsProperty.VALUE_LIST_DELIMITER);
4955                            }
4956                        }
4957
4958                        CmsProperty p;
4959                        if (mapToShared) {
4960                            // map to shared value
4961                            p = new CmsProperty(property, null, result.toString());
4962                        } else {
4963                            // map to individual value
4964                            p = new CmsProperty(property, result.toString(), null);
4965                        }
4966                        // write the created list string value in the selected property
4967                        rootCms.writePropertyObject(filename, p);
4968                        if (mapToShared) {
4969                            // special case: shared mappings must be written only to one sibling, end loop
4970                            i = 0;
4971                        }
4972
4973                    } else if (mapping.startsWith(MAPTO_PROPERTY)) {
4974
4975                        boolean mapToShared;
4976                        int prefixLength;
4977                        // check which mapping is used (shared or individual)
4978                        if (mapping.startsWith(MAPTO_PROPERTY_SHARED)) {
4979                            mapToShared = true;
4980                            prefixLength = MAPTO_PROPERTY_SHARED.length();
4981                        } else if (mapping.startsWith(MAPTO_PROPERTY_INDIVIDUAL)) {
4982                            mapToShared = false;
4983                            prefixLength = MAPTO_PROPERTY_INDIVIDUAL.length();
4984                        } else {
4985                            mapToShared = false;
4986                            prefixLength = MAPTO_PROPERTY.length();
4987                        }
4988
4989                        // this is a property mapping
4990                        String property = mapping.substring(prefixLength);
4991
4992                        CmsProperty p;
4993                        if (mapToShared) {
4994                            // map to shared value
4995                            p = new CmsProperty(property, null, stringValue);
4996                        } else {
4997                            // map to individual value
4998                            p = new CmsProperty(property, stringValue, null);
4999                        }
5000                        // just store the string value in the selected property
5001                        rootCms.writePropertyObject(filename, p);
5002                        if (mapToShared) {
5003                            // special case: shared mappings must be written only to one sibling, end loop
5004                            i = 0;
5005                        }
5006                    } else if (mapping.startsWith(MAPTO_URLNAME)) {
5007                        // we write the actual mappings later
5008                        urlNameMappingResources.add(siblings.get(i));
5009                    }
5010                }
5011            }
5012        }
5013        if (mapToUrlName) {
5014            for (CmsResource resourceForUrlNameMapping : urlNameMappingResources) {
5015                if (!CmsResource.isTemporaryFileName(resourceForUrlNameMapping.getRootPath())) {
5016                    String mappedName = stringValue;
5017                    if (!CmsStringUtil.isEmptyOrWhitespaceOnly(mappedName)) {
5018                        mappedName = mappedName.trim();
5019                        mappingContext.addUrlNameMapping(
5020                            mappedName,
5021                            valueLocale,
5022                            resourceForUrlNameMapping.getStructureId());
5023                    }
5024                }
5025            }
5026        }
5027
5028        // make sure the original is locked
5029        CmsLock lock = rootCms.getLock(file);
5030        if (lock.isUnlocked()) {
5031            rootCms.lockResource(file.getRootPath());
5032        } else if (!lock.isExclusiveOwnedBy(rootCms.getRequestContext().getCurrentUser())) {
5033            rootCms.changeLock(file.getRootPath());
5034        }
5035    }
5036
5037    /**
5038     * Parses a boolean from a string and returns a default value if the string couldn't be parsed.<p>
5039     *
5040     * @param text the text from which to get the boolean value
5041     * @param defaultValue the value to return if parsing fails
5042     *
5043     * @return the parsed boolean
5044     */
5045    private boolean safeParseBoolean(String text, boolean defaultValue) {
5046
5047        if (text == null) {
5048            return defaultValue;
5049        }
5050        try {
5051            return Boolean.parseBoolean(text);
5052        } catch (Throwable t) {
5053            return defaultValue;
5054        }
5055    }
5056
5057}