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, 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.containerpage.shared.CmsFormatterConfig; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProperty; 033import org.opencms.file.CmsResource; 034import org.opencms.file.types.CmsResourceTypeXmlContent; 035import org.opencms.i18n.CmsMultiMessages; 036import org.opencms.json.JSONException; 037import org.opencms.json.JSONObject; 038import org.opencms.main.CmsException; 039import org.opencms.main.CmsLog; 040import org.opencms.main.OpenCms; 041import org.opencms.relations.CmsLink; 042import org.opencms.relations.CmsRelationType; 043import org.opencms.search.galleries.CmsGalleryNameMacroResolver; 044import org.opencms.util.CmsMacroResolver; 045import org.opencms.util.CmsStringUtil; 046import org.opencms.util.CmsUUID; 047import org.opencms.util.I_CmsMacroResolver; 048import org.opencms.xml.CmsXmlContentDefinition; 049import org.opencms.xml.CmsXmlGenericWrapper; 050import org.opencms.xml.CmsXmlUtils; 051import org.opencms.xml.containerpage.I_CmsFormatterBean; 052import org.opencms.xml.content.CmsXmlContentProperty.PropType; 053import org.opencms.xml.page.CmsXmlPage; 054import org.opencms.xml.types.CmsXmlNestedContentDefinition; 055import org.opencms.xml.types.CmsXmlVfsFileValue; 056import org.opencms.xml.types.I_CmsXmlContentValue; 057import org.opencms.xml.types.I_CmsXmlSchemaType; 058 059import java.util.ArrayList; 060import java.util.Collections; 061import java.util.HashMap; 062import java.util.Iterator; 063import java.util.LinkedHashMap; 064import java.util.List; 065import java.util.Locale; 066import java.util.Map; 067import java.util.Map.Entry; 068import java.util.SortedMap; 069import java.util.TreeMap; 070import java.util.function.Function; 071 072import javax.servlet.ServletRequest; 073 074import org.apache.commons.logging.Log; 075 076import org.dom4j.Element; 077 078import com.google.common.base.Supplier; 079 080/** 081 * Provides common methods on XML property configuration.<p> 082 * 083 * @since 8.0.0 084 */ 085public final class CmsXmlContentPropertyHelper implements Cloneable { 086 087 /** Element Property json property constants. */ 088 public enum JsonProperty { 089 090 /** Property's default value. */ 091 defaultValue, 092 /** Property's description. */ 093 description, 094 /** Property's error message. */ 095 error, 096 /** Property's nice name. */ 097 niceName, 098 /** Property's validation regular expression. */ 099 ruleRegex, 100 /** Property's validation rule type. */ 101 ruleType, 102 /** Property's type. */ 103 type, 104 /** Property's value. */ 105 value, 106 /** Property's widget. */ 107 widget, 108 /** Property's widget configuration. */ 109 widgetConf; 110 } 111 112 /** The prefix for macros used to acess properties of the current container page. */ 113 public static final String PAGE_PROPERTY_PREFIX = "page-property:"; 114 115 /** If a property has this value, the page-property macro for this property will expand to the empty string instead. */ 116 protected static final Object PROPERTY_EMPTY_MARKER = "-"; 117 118 /** Widget configuration key-value separator constant. */ 119 private static final String CONF_KEYVALUE_SEPARATOR = ":"; 120 121 /** Widget configuration parameter separator constant. */ 122 private static final String CONF_PARAM_SEPARATOR = "\\|"; 123 124 /** The log object for this class. */ 125 private static final Log LOG = CmsLog.getLog(CmsXmlContentPropertyHelper.class); 126 127 /** 128 * Hidden constructor.<p> 129 */ 130 private CmsXmlContentPropertyHelper() { 131 132 // prevent instantiation 133 } 134 135 /** 136 * Converts a map of properties from server format to client format.<p> 137 * 138 * @param cms the CmsObject to use for VFS operations 139 * @param props the map of properties 140 * @param propConfig the property configuration 141 * 142 * @return the converted property map 143 */ 144 public static Map<String, String> convertPropertiesToClientFormat( 145 CmsObject cms, 146 Map<String, String> props, 147 Map<String, CmsXmlContentProperty> propConfig) { 148 149 return convertProperties(cms, props, propConfig, true); 150 } 151 152 /** 153 * Converts a map of properties from client format to server format.<p> 154 * 155 * @param cms the CmsObject to use for VFS operations 156 * @param props the map of properties 157 * @param propConfig the property configuration 158 * 159 * @return the converted property map 160 */ 161 public static Map<String, String> convertPropertiesToServerFormat( 162 CmsObject cms, 163 Map<String, String> props, 164 Map<String, CmsXmlContentProperty> propConfig) { 165 166 return convertProperties(cms, props, propConfig, false); 167 } 168 169 /** 170 * Creates a deep copy of a property configuration map.<p> 171 * 172 * @param propConfig the property configuration which should be copied 173 * 174 * @return a copy of the property configuration 175 */ 176 public static Map<String, CmsXmlContentProperty> copyPropertyConfiguration( 177 Map<String, CmsXmlContentProperty> propConfig) { 178 179 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>(); 180 for (Map.Entry<String, CmsXmlContentProperty> entry : propConfig.entrySet()) { 181 String key = entry.getKey(); 182 CmsXmlContentProperty propDef = entry.getValue(); 183 result.put(key, propDef.copy()); 184 } 185 return result; 186 } 187 188 /** 189 * Looks up an URI in the sitemap and returns either a sitemap entry id (if the URI is a sitemap URI) 190 * or the structure id of a resource (if the URI is a VFS path).<p> 191 * 192 * @param cms the current CMS context 193 * @param uri the URI to look up 194 * @return a sitemap entry id or a structure id 195 * 196 * @throws CmsException if something goes wrong 197 */ 198 public static CmsUUID getIdForUri(CmsObject cms, String uri) throws CmsException { 199 200 return cms.readResource(uri).getStructureId(); 201 } 202 203 /** 204 * Creates and configures a new macro resolver for resolving macros which occur in property definitions.<p> 205 * 206 * @param cms the CMS context 207 * @param contentHandler the content handler which contains the message bundle that should be available in the macro resolver 208 * @param content the XML content object 209 * @param stringtemplateSource provides stringtemplate templates for use in %(stringtemplate:...) macros 210 * @param containerPage the current container page 211 * 212 * @return a new macro resolver 213 */ 214 public static CmsMacroResolver getMacroResolverForProperties( 215 final CmsObject cms, 216 final I_CmsXmlContentHandler contentHandler, 217 final CmsXmlContent content, 218 final Function<String, String> stringtemplateSource, 219 final CmsResource containerPage) { 220 221 Locale locale = OpenCms.getLocaleManager().getBestAvailableLocaleForXmlContent(cms, content.getFile(), content); 222 final CmsGalleryNameMacroResolver resolver = new CmsGalleryNameMacroResolver(cms, content, locale) { 223 224 @SuppressWarnings("synthetic-access") 225 @Override 226 public String getMacroValue(String macro) { 227 228 if (macro.startsWith(PAGE_PROPERTY_PREFIX)) { 229 String remainder = macro.substring(PAGE_PROPERTY_PREFIX.length()); 230 int secondColonPos = remainder.indexOf(":"); 231 String defaultValue = ""; 232 String propName = null; 233 if (secondColonPos >= 0) { 234 propName = remainder.substring(0, secondColonPos); 235 defaultValue = remainder.substring(secondColonPos + 1); 236 } else { 237 propName = remainder; 238 } 239 if (containerPage != null) { 240 try { 241 CmsProperty prop = cms.readPropertyObject(containerPage, propName, true); 242 String propValue = prop.getValue(); 243 if ((propValue == null) || PROPERTY_EMPTY_MARKER.equals(propValue)) { 244 propValue = defaultValue; 245 } 246 return propValue; 247 } catch (CmsException e) { 248 LOG.error(e.getLocalizedMessage(), e); 249 return defaultValue; 250 } 251 } 252 253 } 254 return super.getMacroValue(macro); 255 } 256 257 }; 258 259 resolver.setStringTemplateSource(stringtemplateSource); 260 Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms); 261 CmsMultiMessages messages = new CmsMultiMessages(wpLocale); 262 messages.addMessages(OpenCms.getWorkplaceManager().getMessages(wpLocale)); 263 messages.addMessages(content.getContentDefinition().getContentHandler().getMessages(wpLocale)); 264 resolver.setCmsObject(cms); 265 resolver.setKeepEmptyMacros(true); 266 resolver.setMessages(messages); 267 return resolver; 268 } 269 270 /** 271 * Returns the property information for the given resource (type) AND the current user.<p> 272 * 273 * @param cms the current CMS context 274 * @param page the current container page 275 * @param resource the resource 276 * 277 * @return the property information 278 * 279 * @throws CmsException if something goes wrong 280 */ 281 public static Map<String, CmsXmlContentProperty> getPropertyInfo( 282 CmsObject cms, 283 CmsResource page, 284 CmsResource resource) 285 throws CmsException { 286 287 if (CmsResourceTypeXmlContent.isXmlContent(resource)) { 288 I_CmsXmlContentHandler contentHandler = CmsXmlContentDefinition.getContentHandlerForResource(cms, resource); 289 Map<String, CmsXmlContentProperty> propertiesConf = contentHandler.getSettings(cms, resource); 290 CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, cms.readFile(resource)); 291 CmsMacroResolver resolver = getMacroResolverForProperties(cms, contentHandler, content, null, page); 292 return resolveMacrosInProperties(propertiesConf, resolver); 293 } 294 return Collections.<String, CmsXmlContentProperty> emptyMap(); 295 } 296 297 /** 298 * Returns a converted property value depending on the given type.<p> 299 * 300 * If the type is {@link CmsXmlContentProperty.PropType#vfslist}, the value is parsed as a 301 * list of paths and converted to a list of IDs.<p> 302 * 303 * @param cms the current CMS context 304 * @param type the property type 305 * @param value the raw property value 306 * 307 * @return a converted property value depending on the given type 308 */ 309 public static String getPropValueIds(CmsObject cms, String type, String value) { 310 311 if (PropType.isVfsList(type)) { 312 return convertPathsToIds(cms, value); 313 } 314 return value; 315 } 316 317 /** 318 * Returns a converted property value depending on the given type.<p> 319 * 320 * If the type is {@link CmsXmlContentProperty.PropType#vfslist}, the value is parsed as a 321 * list of IDs and converted to a list of paths.<p> 322 * 323 * @param cms the current CMS context 324 * @param type the property type 325 * @param value the raw property value 326 * 327 * @return a converted property value depending on the given type 328 */ 329 public static String getPropValuePaths(CmsObject cms, String type, String value) { 330 331 if (PropType.isVfsList(type)) { 332 return convertIdsToPaths(cms, value); 333 } 334 return value; 335 } 336 337 /** 338 * Returns a sitemap or VFS path given a sitemap entry id or structure id.<p> 339 * 340 * This method first tries to read a sitemap entry with the given id. If this succeeds, 341 * the sitemap entry's sitemap path will be returned. If it fails, the method interprets 342 * the id as a structure id and tries to read the corresponding resource, and then returns 343 * its VFS path.<p> 344 * 345 * @param cms the CMS context 346 * @param id a sitemap entry id or structure id 347 * 348 * @return a sitemap or VFS uri 349 * 350 * @throws CmsException if something goes wrong 351 */ 352 public static String getUriForId(CmsObject cms, CmsUUID id) throws CmsException { 353 354 CmsResource res = cms.readResource(id); 355 return cms.getSitePath(res); 356 } 357 358 /** 359 * Returns the widget configuration string parsed into a JSONObject.<p> 360 * 361 * The configuration string should be a map of key value pairs separated by ':' and '|': KEY_1:VALUE_1|KEY_2:VALUE_2 ... 362 * 363 * @param widgetConfiguration the configuration to parse 364 * 365 * @return the configuration JSON 366 */ 367 public static JSONObject getWidgetConfigurationAsJSON(String widgetConfiguration) { 368 369 JSONObject result = new JSONObject(); 370 if (CmsStringUtil.isEmptyOrWhitespaceOnly(widgetConfiguration)) { 371 return result; 372 } 373 Map<String, String> confEntries = CmsStringUtil.splitAsMap( 374 widgetConfiguration, 375 CONF_PARAM_SEPARATOR, 376 CONF_KEYVALUE_SEPARATOR); 377 for (Map.Entry<String, String> entry : confEntries.entrySet()) { 378 try { 379 result.put(entry.getKey(), entry.getValue()); 380 } catch (JSONException e) { 381 // should never happen 382 LOG.error( 383 Messages.get().container(Messages.ERR_XMLCONTENT_UNKNOWN_ELEM_PATH_SCHEMA_1, widgetConfiguration), 384 e); 385 } 386 } 387 return result; 388 } 389 390 /** 391 * Extends the given properties with the default values 392 * from the resource's property configuration.<p> 393 * 394 * @param cms the current CMS context 395 * @param resource the resource to get the property configuration from 396 * @param properties the properties to extend 397 * @param locale the content locale 398 * @param request the current request, if available 399 * 400 * @return a merged map of properties 401 */ 402 public static Map<String, String> mergeDefaults( 403 CmsObject cms, 404 CmsResource resource, 405 Map<String, String> properties, 406 Locale locale, 407 ServletRequest request) { 408 409 Map<String, CmsXmlContentProperty> propertyConfig = null; 410 if (CmsResourceTypeXmlContent.isXmlContent(resource)) { 411 I_CmsFormatterBean formatter = null; 412 // check formatter configuration setting 413 for (Entry<String, String> property : properties.entrySet()) { 414 if (property.getKey().startsWith(CmsFormatterConfig.FORMATTER_SETTINGS_KEY) 415 && CmsUUID.isValidUUID(property.getValue())) { 416 formatter = OpenCms.getADEManager().getCachedFormatters( 417 cms.getRequestContext().getCurrentProject().isOnlineProject()).getFormatters().get( 418 new CmsUUID(property.getValue())); 419 break; 420 } 421 422 } 423 424 try { 425 426 if (formatter != null) { 427 propertyConfig = OpenCms.getADEManager().getFormatterSettings( 428 cms, 429 formatter, 430 resource, 431 locale, 432 request); 433 } else { 434 // fall back to schema configuration 435 propertyConfig = CmsXmlContentDefinition.getContentHandlerForResource(cms, resource).getSettings( 436 cms, 437 resource); 438 } 439 } catch (CmsException e) { 440 // should never happen 441 LOG.error(e.getLocalizedMessage(), e); 442 } 443 } 444 return mergeDefaults(cms, propertyConfig, properties); 445 } 446 447 /** 448 * Extends the given properties with the default values 449 * from property configuration.<p> 450 * 451 * @param cms the current CMS context 452 * @param propertyConfig the property configuration 453 * @param properties the properties to extend 454 * 455 * @return a merged map of properties 456 */ 457 public static Map<String, String> mergeDefaults( 458 CmsObject cms, 459 Map<String, CmsXmlContentProperty> propertyConfig, 460 Map<String, String> properties) { 461 462 Map<String, String> result = new HashMap<String, String>(); 463 if (propertyConfig != null) { 464 for (Map.Entry<String, CmsXmlContentProperty> entry : propertyConfig.entrySet()) { 465 CmsXmlContentProperty prop = entry.getValue(); 466 String value = getPropValueIds(cms, prop.getType(), prop.getDefault()); 467 if (value != null) { 468 result.put(entry.getKey(), value); 469 } 470 } 471 } 472 result.putAll(properties); 473 return result; 474 } 475 476 /** 477 * Reads property nodes from the given location.<p> 478 * 479 * @param cms the current cms context 480 * @param baseLocation the base location 481 * 482 * @return the properties 483 */ 484 public static Map<String, String> readProperties(CmsObject cms, I_CmsXmlContentLocation baseLocation) { 485 486 Map<String, String> result = new HashMap<String, String>(); 487 String elementName = CmsXmlContentProperty.XmlNode.Properties.name(); 488 String nameElementName = CmsXmlContentProperty.XmlNode.Name.name(); 489 List<I_CmsXmlContentValueLocation> propertyLocations = baseLocation.getSubValues(elementName); 490 for (I_CmsXmlContentValueLocation propertyLocation : propertyLocations) { 491 I_CmsXmlContentValueLocation nameLocation = propertyLocation.getSubValue(nameElementName); 492 String name = nameLocation.asString(cms).trim(); 493 String value = null; 494 I_CmsXmlContentValueLocation valueLocation = propertyLocation.getSubValue( 495 CmsXmlContentProperty.XmlNode.Value.name()); 496 I_CmsXmlContentValueLocation stringLocation = valueLocation.getSubValue( 497 CmsXmlContentProperty.XmlNode.String.name()); 498 I_CmsXmlContentValueLocation fileListLocation = valueLocation.getSubValue( 499 CmsXmlContentProperty.XmlNode.FileList.name()); 500 if (stringLocation != null) { 501 value = stringLocation.asString(cms).trim(); 502 } else if (fileListLocation != null) { 503 List<CmsUUID> idList = new ArrayList<CmsUUID>(); 504 List<I_CmsXmlContentValueLocation> fileLocations = fileListLocation.getSubValues( 505 CmsXmlContentProperty.XmlNode.Uri.name()); 506 for (I_CmsXmlContentValueLocation fileLocation : fileLocations) { 507 CmsUUID structureId = fileLocation.asId(cms); 508 idList.add(structureId); 509 } 510 value = CmsStringUtil.listAsString(idList, CmsXmlContentProperty.PROP_SEPARATOR); 511 } 512 if (value != null) { 513 result.put(name, value); 514 } 515 } 516 return result; 517 } 518 519 /** 520 * Reads the properties from property-enabled xml content values.<p> 521 * 522 * @param xmlContent the xml content 523 * @param locale the current locale 524 * @param element the xml element 525 * @param elemPath the xpath 526 * @param elemDef the element definition 527 * 528 * @return the read property map 529 * 530 * @see org.opencms.xml.containerpage.CmsXmlContainerPage.XmlNode#Elements 531 */ 532 public static Map<String, String> readProperties( 533 CmsXmlContent xmlContent, 534 Locale locale, 535 Element element, 536 String elemPath, 537 CmsXmlContentDefinition elemDef) { 538 539 Map<String, String> propertiesMap = new HashMap<String, String>(); 540 // Properties 541 for (Iterator<Element> itProps = CmsXmlGenericWrapper.elementIterator( 542 element, 543 CmsXmlContentProperty.XmlNode.Properties.name()); itProps.hasNext();) { 544 Element property = itProps.next(); 545 546 // property itself 547 int propIndex = CmsXmlUtils.getXpathIndexInt(property.getUniquePath(element)); 548 String propPath = CmsXmlUtils.concatXpath( 549 elemPath, 550 CmsXmlUtils.createXpathElement(property.getName(), propIndex)); 551 I_CmsXmlSchemaType propSchemaType = elemDef.getSchemaType(property.getName()); 552 I_CmsXmlContentValue propValue = propSchemaType.createValue(xmlContent, property, locale); 553 xmlContent.addBookmarkForValue(propValue, propPath, locale, true); 554 CmsXmlContentDefinition propDef = ((CmsXmlNestedContentDefinition)propSchemaType).getNestedContentDefinition(); 555 556 // name 557 Element propName = property.element(CmsXmlContentProperty.XmlNode.Name.name()); 558 xmlContent.addBookmarkForElement(propName, locale, property, propPath, propDef); 559 560 // choice value 561 Element value = property.element(CmsXmlContentProperty.XmlNode.Value.name()); 562 if (value == null) { 563 // this can happen when adding the elements node to the xml content 564 continue; 565 } 566 int valueIndex = CmsXmlUtils.getXpathIndexInt(value.getUniquePath(property)); 567 String valuePath = CmsXmlUtils.concatXpath( 568 propPath, 569 CmsXmlUtils.createXpathElement(value.getName(), valueIndex)); 570 I_CmsXmlSchemaType valueSchemaType = propDef.getSchemaType(value.getName()); 571 I_CmsXmlContentValue valueValue = valueSchemaType.createValue(xmlContent, value, locale); 572 xmlContent.addBookmarkForValue(valueValue, valuePath, locale, true); 573 CmsXmlContentDefinition valueDef = ((CmsXmlNestedContentDefinition)valueSchemaType).getNestedContentDefinition(); 574 575 String val = null; 576 Element string = value.element(CmsXmlContentProperty.XmlNode.String.name()); 577 if (string != null) { 578 // string value 579 xmlContent.addBookmarkForElement(string, locale, value, valuePath, valueDef); 580 val = string.getTextTrim(); 581 } else { 582 // file list value 583 Element valueFileList = value.element(CmsXmlContentProperty.XmlNode.FileList.name()); 584 if (valueFileList == null) { 585 // this can happen when adding the elements node to the xml content 586 continue; 587 } 588 int valueFileListIndex = CmsXmlUtils.getXpathIndexInt(valueFileList.getUniquePath(value)); 589 String valueFileListPath = CmsXmlUtils.concatXpath( 590 valuePath, 591 CmsXmlUtils.createXpathElement(valueFileList.getName(), valueFileListIndex)); 592 I_CmsXmlSchemaType valueFileListSchemaType = valueDef.getSchemaType(valueFileList.getName()); 593 I_CmsXmlContentValue valueFileListValue = valueFileListSchemaType.createValue( 594 xmlContent, 595 valueFileList, 596 locale); 597 xmlContent.addBookmarkForValue(valueFileListValue, valueFileListPath, locale, true); 598 CmsXmlContentDefinition valueFileListDef = ((CmsXmlNestedContentDefinition)valueFileListSchemaType).getNestedContentDefinition(); 599 600 List<CmsUUID> idList = new ArrayList<CmsUUID>(); 601 // files 602 for (Iterator<Element> itFiles = CmsXmlGenericWrapper.elementIterator( 603 valueFileList, 604 CmsXmlContentProperty.XmlNode.Uri.name()); itFiles.hasNext();) { 605 606 Element valueUri = itFiles.next(); 607 xmlContent.addBookmarkForElement( 608 valueUri, 609 locale, 610 valueFileList, 611 valueFileListPath, 612 valueFileListDef); 613 Element valueUriLink = valueUri.element(CmsXmlPage.NODE_LINK); 614 CmsUUID fileId = null; 615 if (valueUriLink == null) { 616 // this can happen when adding the elements node to the xml content 617 // it is not dangerous since the link has to be set before saving 618 } else { 619 fileId = new CmsLink(valueUriLink).getStructureId(); 620 idList.add(fileId); 621 } 622 } 623 // comma separated list of UUIDs 624 val = CmsStringUtil.listAsString(idList, CmsXmlContentProperty.PROP_SEPARATOR); 625 } 626 627 propertiesMap.put(propName.getTextTrim(), val); 628 } 629 return propertiesMap; 630 } 631 632 /** 633 * Resolves macros in the given property information for the given resource (type) AND the current user.<p> 634 * 635 * @param cms the current CMS context 636 * @param page the current container page 637 * @param resource the resource 638 * @param contentGetter loads the actual content 639 * @param propertiesConf the property information 640 * 641 * @return the property information 642 * 643 * @throws CmsException if something goes wrong 644 */ 645 public static Map<String, CmsXmlContentProperty> resolveMacrosForPropertyInfo( 646 CmsObject cms, 647 CmsResource page, 648 CmsResource resource, 649 Supplier<CmsXmlContent> contentGetter, 650 Function<String, String> stringtemplateSource, 651 Map<String, CmsXmlContentProperty> propertiesConf) 652 throws CmsException { 653 654 if (CmsResourceTypeXmlContent.isXmlContent(resource)) { 655 I_CmsXmlContentHandler contentHandler = CmsXmlContentDefinition.getContentHandlerForResource(cms, resource); 656 CmsMacroResolver resolver = getMacroResolverForProperties( 657 cms, 658 contentHandler, 659 contentGetter.get(), 660 stringtemplateSource, 661 page); 662 return resolveMacrosInProperties(propertiesConf, resolver); 663 } 664 return propertiesConf; 665 } 666 667 /** 668 * Resolves macros in all properties in a map.<p> 669 * 670 * @param properties the map of properties in which macros should be resolved 671 * @param resolver the macro resolver to use 672 * 673 * @return a new map of properties with resolved macros 674 */ 675 public static Map<String, CmsXmlContentProperty> resolveMacrosInProperties( 676 Map<String, CmsXmlContentProperty> properties, 677 I_CmsMacroResolver resolver) { 678 679 Map<String, CmsXmlContentProperty> result = new LinkedHashMap<String, CmsXmlContentProperty>(); 680 for (Map.Entry<String, CmsXmlContentProperty> entry : properties.entrySet()) { 681 String key = entry.getKey(); 682 CmsXmlContentProperty prop = entry.getValue(); 683 result.put(key, resolveMacrosInProperty(prop, resolver)); 684 } 685 return result; 686 } 687 688 /** 689 * Resolves the macros in a single property.<p> 690 * 691 * @param property the property in which macros should be resolved 692 * @param resolver the macro resolver to use 693 * 694 * @return a new property with resolved macros 695 */ 696 public static CmsXmlContentProperty resolveMacrosInProperty( 697 CmsXmlContentProperty property, 698 I_CmsMacroResolver resolver) { 699 700 String propName = property.getName(); 701 CmsXmlContentProperty result = new CmsXmlContentProperty( 702 propName, 703 property.getType(), 704 resolver.resolveMacros(property.getWidget()), 705 resolver.resolveMacros(property.getWidgetConfiguration()), 706 property.getRuleRegex(), 707 property.getRuleType(), 708 property.getDefault(), 709 resolver.resolveMacros(property.getNiceName()), 710 resolver.resolveMacros(property.getDescription()), 711 resolver.resolveMacros(property.getError()), 712 property.isPreferFolder() ? "true" : "false"); 713 return result; 714 } 715 716 /** 717 * Saves the given properties to the given xml element.<p> 718 * 719 * @param cms the current CMS context 720 * @param parentElement the parent xml element 721 * @param properties the properties to save, if there is a list of resources, every entry can be a site path or a UUID 722 * @param propertiesConf the configuration of the properties 723 */ 724 public static void saveProperties( 725 CmsObject cms, 726 Element parentElement, 727 Map<String, String> properties, 728 Map<String, CmsXmlContentProperty> propertiesConf) { 729 730 // remove old entries 731 for (Object propElement : parentElement.elements(CmsXmlContentProperty.XmlNode.Properties.name())) { 732 parentElement.remove((Element)propElement); 733 } 734 735 // use a sorted map to force a defined order 736 SortedMap<String, String> props = new TreeMap<String, String>(properties); 737 738 // create new entries 739 for (Map.Entry<String, String> property : props.entrySet()) { 740 String propName = property.getKey(); 741 String propValue = property.getValue(); 742 if ((propValue == null) || (propValue.length() == 0)) { 743 continue; 744 } 745 // only if the property is configured in the schema we will save it 746 Element propElement = parentElement.addElement(CmsXmlContentProperty.XmlNode.Properties.name()); 747 748 // the property name 749 propElement.addElement(CmsXmlContentProperty.XmlNode.Name.name()).addCDATA(propName); 750 Element valueElement = propElement.addElement(CmsXmlContentProperty.XmlNode.Value.name()); 751 boolean isVfs = false; 752 CmsXmlContentProperty propDef = propertiesConf.get(propName); 753 if (propDef != null) { 754 isVfs = CmsXmlContentProperty.PropType.isVfsList(propDef.getType()); 755 } 756 if (!isVfs) { 757 // string value 758 valueElement.addElement(CmsXmlContentProperty.XmlNode.String.name()).addCDATA(propValue); 759 } else { 760 addFileListPropertyValue(cms, valueElement, propValue); 761 } 762 } 763 } 764 765 /** 766 * Adds the XML for a property value of a property of type 'vfslist' to the DOM.<p> 767 * 768 * @param cms the current CMS context 769 * @param valueElement the element to which the vfslist property value should be added 770 * @param propValue the property value which should be saved 771 */ 772 protected static void addFileListPropertyValue(CmsObject cms, Element valueElement, String propValue) { 773 774 // resource list value 775 Element filelistElem = valueElement.addElement(CmsXmlContentProperty.XmlNode.FileList.name()); 776 for (String strId : CmsStringUtil.splitAsList(propValue, CmsXmlContentProperty.PROP_SEPARATOR)) { 777 try { 778 Element fileValueElem = filelistElem.addElement(CmsXmlContentProperty.XmlNode.Uri.name()); 779 CmsVfsFileValueBean fileValue = getFileValueForIdOrUri(cms, strId); 780 // HACK: here we assume weak relations, but it would be more robust to check it, with smth like: 781 // type = xmlContent.getContentDefinition().getContentHandler().getRelationType(fileValueElem.getPath()); 782 CmsRelationType type = CmsRelationType.XML_WEAK; 783 CmsXmlVfsFileValue.fillEntry(fileValueElem, fileValue.getId(), fileValue.getPath(), type); 784 } catch (CmsException e) { 785 // should never happen 786 LOG.error(e.getLocalizedMessage(), e); 787 } 788 } 789 } 790 791 /** 792 * Converts a string containing zero or more structure ids into a string containing the corresponding VFS paths.<p> 793 * 794 * @param cms the CmsObject to use for the VFS operations 795 * @param value a string representation of a list of ids 796 * 797 * @return a string representation of a list of paths 798 */ 799 protected static String convertIdsToPaths(CmsObject cms, String value) { 800 801 if (value == null) { 802 return null; 803 } 804 // represent vfslists as lists of path in JSON 805 List<String> ids = CmsStringUtil.splitAsList(value, CmsXmlContentProperty.PROP_SEPARATOR); 806 List<String> paths = new ArrayList<String>(); 807 for (String id : ids) { 808 try { 809 String path = getUriForId(cms, new CmsUUID(id)); 810 paths.add(path); 811 } catch (Exception e) { 812 // should never happen 813 LOG.error(e.getLocalizedMessage(), e); 814 continue; 815 } 816 } 817 return CmsStringUtil.listAsString(paths, CmsXmlContentProperty.PROP_SEPARATOR); 818 } 819 820 /** 821 * Converts a string containing zero or more VFS paths into a string containing the corresponding structure ids.<p> 822 * 823 * @param cms the CmsObject to use for the VFS operations 824 * @param value a string representation of a list of paths 825 * 826 * @return a string representation of a list of ids 827 */ 828 protected static String convertPathsToIds(CmsObject cms, String value) { 829 830 if (value == null) { 831 return null; 832 } 833 // represent vfslists as lists of path in JSON 834 List<String> paths = CmsStringUtil.splitAsList(value, CmsXmlContentProperty.PROP_SEPARATOR); 835 List<String> ids = new ArrayList<String>(); 836 for (String path : paths) { 837 try { 838 CmsUUID id = getIdForUri(cms, path); 839 ids.add(id.toString()); 840 } catch (CmsException e) { 841 // should never happen 842 LOG.error(e.getLocalizedMessage(), e); 843 continue; 844 } 845 } 846 return CmsStringUtil.listAsString(ids, CmsXmlContentProperty.PROP_SEPARATOR); 847 } 848 849 /** 850 * Helper method for converting a map of properties from client format to server format or vice versa.<p> 851 * 852 * @param cms the CmsObject to use for VFS operations 853 * @param props the map of properties 854 * @param propConfig the property configuration 855 * @param toClient if true, convert from server to client, else from client to server 856 * 857 * @return the converted property map 858 */ 859 protected static Map<String, String> convertProperties( 860 CmsObject cms, 861 Map<String, String> props, 862 Map<String, CmsXmlContentProperty> propConfig, 863 boolean toClient) { 864 865 Map<String, String> result = new HashMap<String, String>(); 866 for (Map.Entry<String, String> entry : props.entrySet()) { 867 String propName = entry.getKey(); 868 String propValue = entry.getValue(); 869 String type = "string"; 870 CmsXmlContentProperty configEntry = getPropertyConfig(propConfig, propName); 871 if (configEntry != null) { 872 type = configEntry.getType(); 873 } 874 String newValue = convertStringPropertyValue(cms, propValue, type, toClient); 875 result.put(propName, newValue); 876 } 877 return result; 878 } 879 880 /** 881 * Converts a property value given as a string between server format and client format.<p> 882 * 883 * @param cms the current CMS context 884 * @param propValue the property value to convert 885 * @param type the type of the property 886 * @param toClient if true, convert to client format, else convert to server format 887 * 888 * @return the converted property value 889 */ 890 protected static String convertStringPropertyValue(CmsObject cms, String propValue, String type, boolean toClient) { 891 892 if (propValue == null) { 893 return null; 894 } 895 if (toClient) { 896 return CmsXmlContentPropertyHelper.getPropValuePaths(cms, type, propValue); 897 } else { 898 return CmsXmlContentPropertyHelper.getPropValueIds(cms, type, propValue); 899 } 900 } 901 902 /** 903 * Given a string which might be a id or a (sitemap or VFS) URI, this method will return 904 * a bean containing the right (sitemap or vfs) root path and (sitemap entry or structure) id.<p> 905 * 906 * @param cms the current CMS context 907 * @param idOrUri a string containing an id or an URI 908 * 909 * @return a bean containing a root path and an id 910 * 911 * @throws CmsException if something goes wrong 912 */ 913 protected static CmsVfsFileValueBean getFileValueForIdOrUri(CmsObject cms, String idOrUri) throws CmsException { 914 915 CmsVfsFileValueBean result; 916 if (CmsUUID.isValidUUID(idOrUri)) { 917 CmsUUID id = new CmsUUID(idOrUri); 918 String uri = getUriForId(cms, id); 919 result = new CmsVfsFileValueBean(cms.getRequestContext().addSiteRoot(uri), id); 920 } else { 921 String uri = idOrUri; 922 CmsUUID id = getIdForUri(cms, idOrUri); 923 result = new CmsVfsFileValueBean(cms.getRequestContext().addSiteRoot(uri), id); 924 } 925 return result; 926 927 } 928 929 /** 930 * Helper method for accessing the property configuration for a single property.<p> 931 * 932 * This method uses the base name of the property to access the property configuration, 933 * i.e. if propName starts with a '#', the part after the '#' will be used as the key for 934 * the property configuration.<p> 935 * 936 * @param propertyConfig the property configuration map 937 * @param propName the name of a property 938 * @return the property configuration for the given property name 939 */ 940 protected static CmsXmlContentProperty getPropertyConfig( 941 Map<String, CmsXmlContentProperty> propertyConfig, 942 String propName) { 943 944 return propertyConfig.get(propName); 945 } 946 947}