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