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 <solrfield> 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}