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