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