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