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.file; 029 030import org.opencms.i18n.CmsLocaleManager; 031import org.opencms.main.CmsRuntimeException; 032import org.opencms.util.CmsStringUtil; 033 034import java.io.Serializable; 035import java.util.ArrayList; 036import java.util.Collections; 037import java.util.HashMap; 038import java.util.Iterator; 039import java.util.LinkedHashMap; 040import java.util.List; 041import java.util.Locale; 042import java.util.Map; 043import java.util.RandomAccess; 044 045import org.apache.commons.collections.Transformer; 046 047/** 048 * Represents a property (meta-information) mapped to a VFS resource.<p> 049 * 050 * A property is an object that contains three string values: a name, a property value which is mapped 051 * to the structure record of a resource, and a property value which is mapped to the resource 052 * record of a resource. A property object is valid if it has both values or just one value set. 053 * Each property needs at least a name and one value set.<p> 054 * 055 * A property value mapped to the structure record of a resource is significant for a single 056 * resource (sibling). A property value mapped to the resource record of a resource is significant 057 * for all siblings of a resource record. This is possible by getting the "compound value" 058 * (see {@link #getValue()}) of a property in case a property object has both values set. The compound 059 * value of a property object is the value mapped to the structure record, because it's structure 060 * value is more significant than it's resource value. This allows to set a property only one time 061 * on the resource record, and the property takes effect on all siblings of this resource record.<p> 062 * 063 * The ID of the structure or resource record where a property value is mapped to is represented by 064 * the "PROPERTY_MAPPING_ID" table attribute in the database. The "PROPERTY_MAPPING_TYPE" table 065 * attribute (see {@link #STRUCTURE_RECORD_MAPPING} and {@link #RESOURCE_RECORD_MAPPING}) 066 * determines whether the value of the "PROPERTY_MAPPING_ID" attribute of the current row is 067 * a structure or resource record ID.<p> 068 * 069 * Property objects are written to the database using {@link org.opencms.file.CmsObject#writePropertyObject(String, CmsProperty)} 070 * or {@link org.opencms.file.CmsObject#writePropertyObjects(String, List)}, no matter 071 * whether you want to save a new (non-existing) property, update an existing property, or delete an 072 * existing property. To delete a property you would write a property object with either the 073 * structure and/or resource record values set to {@link #DELETE_VALUE} to indicate that a 074 * property value should be deleted in the database. Set property values to null if they should 075 * remain unchanged in the database when a property object is written. As for example you want to 076 * update just the structure value of a property, you would set the structure value to the new string, 077 * and the resource value to null (which is already the case by default).<p> 078 * 079 * Use {@link #setAutoCreatePropertyDefinition(boolean)} to set a boolean flag whether a missing property 080 * definition should be created implicitly for a resource type when a property is written to the database. 081 * The default value for this flag is <code>false</code>. Thus, you receive a CmsException if you try 082 * to write a property of a resource with a resource type which lacks a property definition for 083 * this resource type. It is not a good style to set {@link #setAutoCreatePropertyDefinition(boolean)} 084 * on true to make writing properties to the database work in any case, because then you will loose 085 * control about which resource types support which property definitions.<p> 086 * 087 * @since 6.0.0 088 */ 089public class CmsProperty implements Serializable, Cloneable, Comparable<CmsProperty> { 090 091 /** Transforms a given properties map, to a map where the returned values for a property are 092 * dependent on the locale. 093 */ 094 public static class CmsPropertyLocaleTransformer implements Transformer { 095 096 /** The original properties map. */ 097 private Map<String, String> m_properties; 098 /** The locale, w.r.t. which the properties should be accessed. */ 099 private Locale m_locale; 100 101 /** 102 * Default constructor. 103 * @param properties the "raw" properties map as read for a resource. 104 * @param locale the locale w.r.t. which the properties should be accessed. 105 */ 106 public CmsPropertyLocaleTransformer(Map<String, String> properties, Locale locale) { 107 108 m_properties = null == properties ? new HashMap<String, String>() : properties; 109 m_locale = null == locale ? new Locale("") : locale; 110 } 111 112 /** 113 * @see org.apache.commons.collections.Transformer#transform(java.lang.Object) 114 */ 115 public Object transform(Object propertyName) { 116 117 return readProperty((String)propertyName); 118 } 119 120 /** 121 * Looks up a property in {@link #m_properties}, but returns the localized variant. 122 * 123 * @param propertyName the property to look up 124 * @return the value of the property 125 */ 126 protected String readProperty(String propertyName) { 127 128 if (null == m_locale) { 129 return m_properties.get(propertyName); 130 } else { 131 return m_properties.get(getLocalizedKey(m_properties, propertyName, m_locale)); 132 } 133 } 134 } 135 136 /** 137 * Signals that the resource property values of a resource 138 * should be deleted using deleteAllProperties.<p> 139 */ 140 public static final int DELETE_OPTION_DELETE_RESOURCE_VALUES = 3; 141 142 /** 143 * Signals that both the structure and resource property values of a resource 144 * should be deleted using deleteAllProperties.<p> 145 */ 146 public static final int DELETE_OPTION_DELETE_STRUCTURE_AND_RESOURCE_VALUES = 1; 147 148 /** 149 * Signals that the structure property values of a resource 150 * should be deleted using deleteAllProperties.<p> 151 */ 152 public static final int DELETE_OPTION_DELETE_STRUCTURE_VALUES = 2; 153 154 /** 155 * An empty string to decide that a property value should be deleted when this 156 * property object is written to the database.<p> 157 */ 158 public static final String DELETE_VALUE = ""; 159 160 /** 161 * Value of the "mapping-type" database attribute to indicate that a property value is mapped 162 * to a resource record.<p> 163 */ 164 public static final int RESOURCE_RECORD_MAPPING = 2; 165 166 /** 167 * Value of the "mapping-type" database attribute to indicate that a property value is mapped 168 * to a structure record.<p> 169 */ 170 public static final int STRUCTURE_RECORD_MAPPING = 1; 171 172 /** Key used for a individual (structure) property value. */ 173 public static final String TYPE_INDIVIDUAL = "individual"; 174 175 /** Key used for a shared (resource) property value. */ 176 public static final String TYPE_SHARED = "shared"; 177 178 /** The delimiter value for separating values in a list, per default this is the <code>|</code> char. */ 179 public static final char VALUE_LIST_DELIMITER = '|'; 180 181 /** The list delimiter replacement String used if the delimiter itself is contained in a String value. */ 182 public static final String VALUE_LIST_DELIMITER_REPLACEMENT = "%(ld)"; 183 184 /** The delimiter value for separating values in a map, per default this is the <code>=</code> char. */ 185 public static final char VALUE_MAP_DELIMITER = '='; 186 187 /** The map delimiter replacement String used if the delimiter itself is contained in a String value. */ 188 public static final String VALUE_MAP_DELIMITER_REPLACEMENT = "%(md)"; 189 190 /** The null property object to be used in caches if a property is not found. */ 191 private static final CmsProperty NULL_PROPERTY = new CmsProperty(); 192 193 /** Serial version UID required for safe serialization. */ 194 private static final long serialVersionUID = 93613508924212782L; 195 196 /** 197 * Static initializer required for freezing the <code>{@link #NULL_PROPERTY}</code>.<p> 198 */ 199 static { 200 201 NULL_PROPERTY.m_frozen = true; 202 NULL_PROPERTY.m_name = ""; 203 } 204 205 /** 206 * Boolean flag to decide if the property definition for this property should be created 207 * implicitly on any write operation if doesn't exist already.<p> 208 */ 209 private boolean m_autoCreatePropertyDefinition; 210 211 /** Indicates if the property is frozen (required for <code>NULL_PROPERTY</code>). */ 212 private boolean m_frozen; 213 214 /** The name of this property. */ 215 private String m_name; 216 217 /** The origin root path of the property. */ 218 private String m_origin; 219 220 /** The value of this property attached to the resource record. */ 221 private String m_resourceValue; 222 223 /** The (optional) value list of this property attached to the resource record. */ 224 private List<String> m_resourceValueList; 225 226 /** The (optional) value map of this property attached to the resource record. */ 227 private Map<String, String> m_resourceValueMap; 228 229 /** The value of this property attached to the structure record. */ 230 private String m_structureValue; 231 232 /** The (optional) value list of this property attached to the structure record. */ 233 private List<String> m_structureValueList; 234 235 /** The (optional) value map of this property attached to the structure record. */ 236 private Map<String, String> m_structureValueMap; 237 238 /** 239 * Creates a new CmsProperty object.<p> 240 * 241 * The structure and resource property values are initialized to null. The structure and 242 * resource IDs are initialized to {@link org.opencms.util.CmsUUID#getNullUUID()}.<p> 243 */ 244 public CmsProperty() { 245 246 // nothing to do, all values will be initialized with <code>null</code> or <code>false</code> by default 247 } 248 249 /** 250 * Creates a new CmsProperty object using the provided values.<p> 251 * 252 * If the property definition does not exist for the resource type it 253 * is automatically created when this property is written. 254 * 255 * @param name the name of the property definition 256 * @param structureValue the value to write as structure property 257 * @param resourceValue the value to write as resource property 258 */ 259 public CmsProperty(String name, String structureValue, String resourceValue) { 260 261 this(name, structureValue, resourceValue, true); 262 } 263 264 /** 265 * Creates a new CmsProperty object using the provided values.<p> 266 * 267 * If <code>null</code> is supplied for the resource or structure value, this 268 * value will not be available for this property.<p> 269 * 270 * @param name the name of the property definition 271 * @param structureValue the value to write as structure property, or <code>null</code> 272 * @param resourceValue the value to write as resource property , or <code>null</code> 273 * @param autoCreatePropertyDefinition if <code>true</code>, the property definition for this property will be 274 * created implicitly on any write operation if it doesn't exist already 275 */ 276 public CmsProperty(String name, String structureValue, String resourceValue, boolean autoCreatePropertyDefinition) { 277 278 m_name = name.trim(); 279 m_structureValue = structureValue; 280 m_resourceValue = resourceValue; 281 m_autoCreatePropertyDefinition = autoCreatePropertyDefinition; 282 } 283 284 /** 285 * Searches in a list for the first occurrence of a {@link CmsProperty} object with the given name.<p> 286 * 287 * To check if the "null property" has been returned if a property was 288 * not found, use {@link #isNullProperty()} on the result.<p> 289 * 290 * @param name a property name 291 * @param list a list of {@link CmsProperty} objects 292 * @return the index of the first occurrence of the name in they specified list, 293 * or {@link CmsProperty#getNullProperty()} if the name is not found 294 */ 295 public static final CmsProperty get(String name, List<CmsProperty> list) { 296 297 CmsProperty property = null; 298 name = name.trim(); 299 // choose the fastest method to traverse the list 300 if (list instanceof RandomAccess) { 301 for (int i = 0, n = list.size(); i < n; i++) { 302 property = list.get(i); 303 if (property.m_name.equals(name)) { 304 return property; 305 } 306 } 307 } else { 308 Iterator<CmsProperty> i = list.iterator(); 309 while (i.hasNext()) { 310 property = i.next(); 311 if (property.m_name.equals(name)) { 312 return property; 313 } 314 } 315 } 316 317 return NULL_PROPERTY; 318 } 319 320 /** 321 * Returns the locale specific property name for the provided property base name. 322 * The locale specific extension is added without any check. 323 * E.g., for provided property name "Title" and locale "de_DE", the result will be "Title_de_DE". 324 * If the locale is null, the base name will be returned as is. 325 * If the base name is null, null will be returned. 326 * 327 * @param baseName the base property name. 328 * @param locale the locale to extend the property name for. 329 * @return the locale specific property name. 330 */ 331 public static String getLocaleSpecificPropertyName(String baseName, Locale locale) { 332 333 return baseName == null ? null : (locale == null ? baseName : (baseName + "_" + locale.toString())); 334 } 335 336 /** 337 * Returns the value for the best matching local-specific property version. 338 * 339 * @param propertiesMap the "raw" property map 340 * @param key the name of the property to search for 341 * @param locale the locale to search for 342 * 343 * @return the key for the best matching local-specific property version. 344 */ 345 public static String getLocaleSpecificPropertyValue( 346 Map<String, CmsProperty> propertiesMap, 347 String key, 348 Locale locale) { 349 350 String localeSpecificKey = CmsProperty.getLocalizedKey(propertiesMap, key, locale); 351 if (propertiesMap.containsKey(localeSpecificKey)) { 352 return propertiesMap.get(localeSpecificKey).getValue(); 353 } 354 return null; 355 } 356 357 /** 358 * Returns the key for the best matching local-specific property version. 359 * 360 * @param propertiesMap the "raw" property map 361 * @param key the name of the property to search for 362 * @param locale the locale to search for 363 * 364 * @return the key for the best matching local-specific property version. 365 */ 366 public static String getLocalizedKey(Map<String, ?> propertiesMap, String key, Locale locale) { 367 368 List<String> localizedKeys = CmsLocaleManager.getLocaleVariants(key, locale, true, false); 369 for (String localizedKey : localizedKeys) { 370 if (propertiesMap.containsKey(localizedKey)) { 371 return localizedKey; 372 } 373 } 374 return key; 375 } 376 377 /** 378 * Returns the null property object.<p> 379 * 380 * @return the null property object 381 */ 382 public static final CmsProperty getNullProperty() { 383 384 return NULL_PROPERTY; 385 } 386 387 /** 388 * Transforms a list of CmsProperty objects with structure and resource values into a map with 389 * CmsProperty object values keyed by property keys.<p> 390 * 391 * @param list a list of CmsProperty objects 392 * @return a map with CmsPropery object values keyed by property keys 393 */ 394 public static Map<String, CmsProperty> getPropertyMap(List<CmsProperty> list) { 395 396 Map<String, CmsProperty> result = null; 397 String key = null; 398 CmsProperty property = null; 399 400 if ((list == null) || (list.size() == 0)) { 401 return Collections.emptyMap(); 402 } 403 404 result = new HashMap<String, CmsProperty>(); 405 406 // choose the fastest method to iterate the list 407 if (list instanceof RandomAccess) { 408 for (int i = 0, n = list.size(); i < n; i++) { 409 property = list.get(i); 410 key = property.getName(); 411 result.put(key, property); 412 } 413 } else { 414 Iterator<CmsProperty> i = list.iterator(); 415 while (i.hasNext()) { 416 property = i.next(); 417 key = property.getName(); 418 result.put(key, property); 419 } 420 } 421 422 return result; 423 } 424 425 /** 426 * Calls <code>{@link #setAutoCreatePropertyDefinition(boolean)}</code> for each 427 * property object in the given List with the given <code>value</code> parameter.<p> 428 * 429 * This method will modify the objects in the input list directly.<p> 430 * 431 * @param list a list of {@link CmsProperty} objects to modify 432 * @param value boolean value 433 * 434 * @return the modified list of {@link CmsProperty} objects 435 * 436 * @see #setAutoCreatePropertyDefinition(boolean) 437 */ 438 public static final List<CmsProperty> setAutoCreatePropertyDefinitions(List<CmsProperty> list, boolean value) { 439 440 CmsProperty property; 441 442 // choose the fastest method to traverse the list 443 if (list instanceof RandomAccess) { 444 for (int i = 0, n = list.size(); i < n; i++) { 445 property = list.get(i); 446 property.m_autoCreatePropertyDefinition = value; 447 } 448 } else { 449 Iterator<CmsProperty> i = list.iterator(); 450 while (i.hasNext()) { 451 property = i.next(); 452 property.m_autoCreatePropertyDefinition = value; 453 } 454 } 455 456 return list; 457 } 458 459 /** 460 * Calls <code>{@link #setFrozen(boolean)}</code> for each 461 * {@link CmsProperty} object in the given List if it is not already frozen.<p> 462 * 463 * This method will modify the objects in the input list directly.<p> 464 * 465 * @param list a list of {@link CmsProperty} objects 466 * 467 * @return the modified list of properties 468 * 469 * @see #setFrozen(boolean) 470 */ 471 public static final List<CmsProperty> setFrozen(List<CmsProperty> list) { 472 473 CmsProperty property; 474 475 // choose the fastest method to traverse the list 476 if (list instanceof RandomAccess) { 477 for (int i = 0, n = list.size(); i < n; i++) { 478 property = list.get(i); 479 if (!property.isFrozen()) { 480 property.setFrozen(true); 481 } 482 } 483 } else { 484 Iterator<CmsProperty> i = list.iterator(); 485 while (i.hasNext()) { 486 property = i.next(); 487 if (!property.isFrozen()) { 488 property.setFrozen(true); 489 } 490 } 491 } 492 493 return list; 494 } 495 496 /** 497 * Transforms a Map of String values into a list of 498 * {@link CmsProperty} objects with the property name set from the 499 * Map key, and the structure value set from the Map value.<p> 500 * 501 * @param map a Map with String keys and String values 502 * 503 * @return a list of {@link CmsProperty} objects 504 */ 505 public static List<CmsProperty> toList(Map<String, String> map) { 506 507 if ((map == null) || (map.size() == 0)) { 508 return Collections.emptyList(); 509 } 510 511 List<CmsProperty> result = new ArrayList<CmsProperty>(map.size()); 512 Iterator<Map.Entry<String, String>> i = map.entrySet().iterator(); 513 while (i.hasNext()) { 514 Map.Entry<String, String> e = i.next(); 515 CmsProperty property = new CmsProperty(e.getKey(), e.getValue(), null); 516 result.add(property); 517 } 518 519 return result; 520 } 521 522 /** 523 * Transforms a list of {@link CmsProperty} objects into a Map which uses the property name as 524 * Map key (String), and the property value as Map value (String).<p> 525 * 526 * @param list a list of {@link CmsProperty} objects 527 * 528 * @return a Map which uses the property names as 529 * Map keys (String), and the property values as Map values (String) 530 */ 531 public static Map<String, String> toMap(List<CmsProperty> list) { 532 533 if ((list == null) || (list.size() == 0)) { 534 return Collections.emptyMap(); 535 } 536 537 String name = null; 538 String value = null; 539 CmsProperty property = null; 540 Map<String, String> result = new HashMap<String, String>(list.size()); 541 542 // choose the fastest method to traverse the list 543 if (list instanceof RandomAccess) { 544 for (int i = 0, n = list.size(); i < n; i++) { 545 property = list.get(i); 546 name = property.m_name; 547 value = property.getValue(); 548 result.put(name, value); 549 } 550 } else { 551 Iterator<CmsProperty> i = list.iterator(); 552 while (i.hasNext()) { 553 property = i.next(); 554 name = property.m_name; 555 value = property.getValue(); 556 result.put(name, value); 557 } 558 } 559 560 return result; 561 } 562 563 /** 564 * Stores a collection of properties in a map, with the property names as keys.<p> 565 * 566 * @param properties the properties to store in the map 567 * 568 * @return the map with the property names as keys and the property objects as values 569 */ 570 public static Map<String, CmsProperty> toObjectMap(Iterable<CmsProperty> properties) { 571 572 Map<String, CmsProperty> result = new LinkedHashMap<String, CmsProperty>(); 573 for (CmsProperty property : properties) { 574 result.put(property.getName(), property); 575 } 576 return result; 577 } 578 579 /** 580 * Wraps a null value into a null property, and returns all other values unchanged.<p> 581 * 582 * @param prop the value to wrap 583 * 584 * @return a wrapped null property, or the original prop if it wasn't null 585 */ 586 public static CmsProperty wrapIfNull(CmsProperty prop) { 587 588 if (prop == null) { 589 return getNullProperty(); 590 } else { 591 return prop; 592 } 593 } 594 595 /** 596 * Checks if the property definition for this property will be 597 * created implicitly on any write operation if doesn't already exist.<p> 598 * 599 * @return <code>true</code>, if the property definition for this property will be created implicitly on any write operation 600 */ 601 public boolean autoCreatePropertyDefinition() { 602 603 return m_autoCreatePropertyDefinition; 604 } 605 606 /** 607 * Creates a clone of this property.<p> 608 * 609 * @return a clone of this property 610 * 611 * @see #cloneAsProperty() 612 */ 613 @Override 614 public CmsProperty clone() { 615 616 return cloneAsProperty(); 617 } 618 619 /** 620 * Creates a clone of this property that already is of type <code>{@link CmsProperty}</code>.<p> 621 * 622 * The cloned property will not be frozen.<p> 623 * 624 * @return a clone of this property that already is of type <code>{@link CmsProperty}</code> 625 */ 626 public CmsProperty cloneAsProperty() { 627 628 if (this == NULL_PROPERTY) { 629 // null property must never be cloned 630 return NULL_PROPERTY; 631 } 632 CmsProperty clone = new CmsProperty(); 633 clone.m_name = m_name; 634 clone.m_structureValue = m_structureValue; 635 clone.m_structureValueList = m_structureValueList; 636 clone.m_resourceValue = m_resourceValue; 637 clone.m_resourceValueList = m_resourceValueList; 638 clone.m_autoCreatePropertyDefinition = m_autoCreatePropertyDefinition; 639 clone.m_origin = m_origin; 640 // the value for m_frozen does not need to be set as it is false by default 641 642 return clone; 643 } 644 645 /** 646 * Compares this property to another Object.<p> 647 * 648 * @param obj the other object to be compared 649 * @return if the argument is a property object, returns zero if the name of the argument is equal to the name of this property object, 650 * a value less than zero if the name of this property is lexicographically less than the name of the argument, 651 * or a value greater than zero if the name of this property is lexicographically greater than the name of the argument 652 */ 653 public int compareTo(CmsProperty obj) { 654 655 if (obj == this) { 656 return 0; 657 } 658 return m_name.compareTo(obj.m_name); 659 } 660 661 /** 662 * Tests if a specified object is equal to this CmsProperty object.<p> 663 * 664 * Two property objects are equal if their names are equal.<p> 665 * 666 * In case you want to compare the values as well as the name, 667 * use {@link #isIdentical(CmsProperty)} instead.<p> 668 * 669 * @param obj another object 670 * @return true, if the specified object is equal to this CmsProperty object 671 * 672 * @see #isIdentical(CmsProperty) 673 */ 674 @Override 675 public boolean equals(Object obj) { 676 677 if (obj == this) { 678 return true; 679 } 680 if (obj instanceof CmsProperty) { 681 return ((CmsProperty)obj).m_name.equals(m_name); 682 } 683 return false; 684 } 685 686 /** 687 * Returns the name of this property.<p> 688 * 689 * @return the name of this property 690 */ 691 public String getName() { 692 693 return m_name; 694 } 695 696 /** 697 * Returns the root path of the resource from which the property was read.<p> 698 * 699 * @return the root path of the resource from which the property was read 700 */ 701 public String getOrigin() { 702 703 return m_origin; 704 } 705 706 /** 707 * Returns the value of this property attached to the resource record.<p> 708 * 709 * @return the value of this property attached to the resource record 710 */ 711 public String getResourceValue() { 712 713 return m_resourceValue; 714 } 715 716 /** 717 * Returns the value of this property attached to the resource record, split as a list.<p> 718 * 719 * This list is build form the resource value, which is split into separate values 720 * using the <code>|</code> char as delimiter. If the delimiter is not found, 721 * then the list will contain one entry which is equal to <code>{@link #getResourceValue()}</code>.<p> 722 * 723 * @return the value of this property attached to the resource record, split as a (unmodifiable) list of Strings 724 */ 725 public List<String> getResourceValueList() { 726 727 if ((m_resourceValueList == null) && (m_resourceValue != null)) { 728 // use lazy initializing of the list 729 m_resourceValueList = createListFromValue(m_resourceValue); 730 m_resourceValueList = Collections.unmodifiableList(m_resourceValueList); 731 } 732 return m_resourceValueList; 733 } 734 735 /** 736 * Returns the value of this property attached to the resource record as a map.<p> 737 * 738 * This map is build from the used value, which is split into separate key/value pairs 739 * using the <code>|</code> char as delimiter. If the delimiter is not found, 740 * then the map will contain one entry.<p> 741 * 742 * The key/value pairs are separated with the <code>=</code>.<p> 743 * 744 * @return the value of this property attached to the resource record, as an (unmodifiable) map of Strings 745 */ 746 public Map<String, String> getResourceValueMap() { 747 748 if ((m_resourceValueMap == null) && (m_resourceValue != null)) { 749 // use lazy initializing of the map 750 m_resourceValueMap = createMapFromValue(m_resourceValue); 751 m_resourceValueMap = Collections.unmodifiableMap(m_resourceValueMap); 752 } 753 return m_resourceValueMap; 754 } 755 756 /** 757 * Returns the value of this property attached to the structure record.<p> 758 * 759 * @return the value of this property attached to the structure record 760 */ 761 public String getStructureValue() { 762 763 return m_structureValue; 764 } 765 766 /** 767 * Returns the value of this property attached to the structure record, split as a list.<p> 768 * 769 * This list is build form the structure value, which is split into separate values 770 * using the <code>|</code> char as delimiter. If the delimiter is not found, 771 * then the list will contain one entry which is equal to <code>{@link #getStructureValue()}</code>.<p> 772 * 773 * @return the value of this property attached to the structure record, split as a (unmodifiable) list of Strings 774 */ 775 public List<String> getStructureValueList() { 776 777 if ((m_structureValueList == null) && (m_structureValue != null)) { 778 // use lazy initializing of the list 779 m_structureValueList = createListFromValue(m_structureValue); 780 m_structureValueList = Collections.unmodifiableList(m_structureValueList); 781 } 782 return m_structureValueList; 783 } 784 785 /** 786 * Returns the value of this property attached to the structure record as a map.<p> 787 * 788 * This map is build from the used value, which is split into separate key/value pairs 789 * using the <code>|</code> char as delimiter. If the delimiter is not found, 790 * then the map will contain one entry.<p> 791 * 792 * The key/value pairs are separated with the <code>=</code>.<p> 793 * 794 * @return the value of this property attached to the structure record, as an (unmodifiable) map of Strings 795 */ 796 public Map<String, String> getStructureValueMap() { 797 798 if ((m_structureValueMap == null) && (m_structureValue != null)) { 799 // use lazy initializing of the map 800 m_structureValueMap = createMapFromValue(m_structureValue); 801 m_structureValueMap = Collections.unmodifiableMap(m_structureValueMap); 802 } 803 return m_structureValueMap; 804 } 805 806 /** 807 * Returns the compound value of this property.<p> 808 * 809 * The value returned is the value of {@link #getStructureValue()}, if it is not <code>null</code>. 810 * Otherwise the value if {@link #getResourceValue()} is returned (which may also be <code>null</code>).<p> 811 * 812 * @return the compound value of this property 813 */ 814 public String getValue() { 815 816 return (m_structureValue != null) ? m_structureValue : m_resourceValue; 817 } 818 819 /** 820 * Returns the compound value of this property, or a specified default value, 821 * if both the structure and resource values are null.<p> 822 * 823 * In other words, this method returns the defaultValue if this property object 824 * is the null property (see {@link CmsProperty#getNullProperty()}).<p> 825 * 826 * @param defaultValue a default value which is returned if both the structure and resource values are <code>null</code> 827 * 828 * @return the compound value of this property, or the default value 829 */ 830 public String getValue(String defaultValue) { 831 832 if (this == CmsProperty.NULL_PROPERTY) { 833 // return the default value if this property is the null property 834 return defaultValue; 835 } 836 837 // somebody might have set both values to null manually 838 // on a property object different from the null property... 839 return (m_structureValue != null) 840 ? m_structureValue 841 : ((m_resourceValue != null) ? m_resourceValue : defaultValue); 842 } 843 844 /** 845 * Returns the compound value of this property, split as a list.<p> 846 * 847 * This list is build form the used value, which is split into separate values 848 * using the <code>|</code> char as delimiter. If the delimiter is not found, 849 * then the list will contain one entry.<p> 850 * 851 * The value returned is the value of {@link #getStructureValueList()}, if it is not <code>null</code>. 852 * Otherwise the value of {@link #getResourceValueList()} is returned (which may also be <code>null</code>).<p> 853 * 854 * @return the compound value of this property, split as a (unmodifiable) list of Strings 855 */ 856 public List<String> getValueList() { 857 858 return (m_structureValue != null) ? getStructureValueList() : getResourceValueList(); 859 } 860 861 /** 862 * Returns the compound value of this property, split as a list, or a specified default value list, 863 * if both the structure and resource values are null.<p> 864 * 865 * In other words, this method returns the defaultValue if this property object 866 * is the null property (see {@link CmsProperty#getNullProperty()}).<p> 867 * 868 * @param defaultValue a default value list which is returned if both the structure and resource values are <code>null</code> 869 * 870 * @return the compound value of this property, split as a (unmodifiable) list of Strings 871 */ 872 public List<String> getValueList(List<String> defaultValue) { 873 874 if (this == CmsProperty.NULL_PROPERTY) { 875 // return the default value if this property is the null property 876 return defaultValue; 877 } 878 879 // somebody might have set both values to null manually 880 // on a property object different from the null property... 881 return (m_structureValue != null) 882 ? getStructureValueList() 883 : ((m_resourceValue != null) ? getResourceValueList() : defaultValue); 884 } 885 886 /** 887 * Returns the compound value of this property as a map.<p> 888 * 889 * This map is build from the used value, which is split into separate key/value pairs 890 * using the <code>|</code> char as delimiter. If the delimiter is not found, 891 * then the map will contain one entry.<p> 892 * 893 * The key/value pairs are separated with the <code>=</code>.<p> 894 * 895 * The value returned is the value of {@link #getStructureValueMap()}, if it is not <code>null</code>. 896 * Otherwise the value of {@link #getResourceValueMap()} is returned (which may also be <code>null</code>).<p> 897 * 898 * @return the compound value of this property as a (unmodifiable) map of Strings 899 */ 900 public Map<String, String> getValueMap() { 901 902 return (m_structureValue != null) ? getStructureValueMap() : getResourceValueMap(); 903 } 904 905 /** 906 * Returns the compound value of this property as a map, or a specified default value map, 907 * if both the structure and resource values are null.<p> 908 * 909 * In other words, this method returns the defaultValue if this property object 910 * is the null property (see {@link CmsProperty#getNullProperty()}).<p> 911 * 912 * @param defaultValue a default value map which is returned if both the structure and resource values are <code>null</code> 913 * 914 * @return the compound value of this property as a (unmodifiable) map of Strings 915 */ 916 public Map<String, String> getValueMap(Map<String, String> defaultValue) { 917 918 if (this == CmsProperty.NULL_PROPERTY) { 919 // return the default value if this property is the null property 920 return defaultValue; 921 } 922 923 // somebody might have set both values to null manually 924 // on a property object different from the null property... 925 return (m_structureValue != null) 926 ? getStructureValueMap() 927 : ((m_resourceValue != null) ? getResourceValueMap() : defaultValue); 928 } 929 930 /** 931 * Returns the hash code of the property, which is based only on the property name, not on the values.<p> 932 * 933 * The resource and structure values are not taken into consideration for the hashcode generation 934 * because the {@link #equals(Object)} implementation also does not take these into consideration.<p> 935 * 936 * @return the hash code of the property 937 * 938 * @see java.lang.Object#hashCode() 939 */ 940 @Override 941 public int hashCode() { 942 943 return m_name.hashCode(); 944 } 945 946 /** 947 * Checks if the resource value of this property should be deleted when this 948 * property object is written to the database.<p> 949 * 950 * @return true, if the resource value of this property should be deleted 951 * @see CmsProperty#DELETE_VALUE 952 */ 953 public boolean isDeleteResourceValue() { 954 955 return (m_resourceValue == DELETE_VALUE) || ((m_resourceValue != null) && (m_resourceValue.length() == 0)); 956 } 957 958 /** 959 * Checks if the structure value of this property should be deleted when this 960 * property object is written to the database.<p> 961 * 962 * @return true, if the structure value of this property should be deleted 963 * @see CmsProperty#DELETE_VALUE 964 */ 965 public boolean isDeleteStructureValue() { 966 967 return (m_structureValue == DELETE_VALUE) || ((m_structureValue != null) && (m_structureValue.length() == 0)); 968 } 969 970 /** 971 * Returns <code>true</code> if this property is frozen, that is read only.<p> 972 * 973 * @return <code>true</code> if this property is frozen, that is read only 974 */ 975 public boolean isFrozen() { 976 977 return m_frozen; 978 } 979 980 /** 981 * Tests if a given CmsProperty is identical to this CmsProperty object.<p> 982 * 983 * The property object are identical if their name, structure and 984 * resource values are all equals.<p> 985 * 986 * @param property another property object 987 * @return true, if the specified object is equal to this CmsProperty object 988 */ 989 public boolean isIdentical(CmsProperty property) { 990 991 boolean isEqual; 992 993 // compare the name 994 if (m_name == null) { 995 isEqual = (property.getName() == null); 996 } else { 997 isEqual = m_name.equals(property.getName()); 998 } 999 1000 // compare the structure value 1001 if (m_structureValue == null) { 1002 isEqual &= (property.getStructureValue() == null); 1003 } else { 1004 isEqual &= m_structureValue.equals(property.getStructureValue()); 1005 } 1006 1007 // compare the resource value 1008 if (m_resourceValue == null) { 1009 isEqual &= (property.getResourceValue() == null); 1010 } else { 1011 isEqual &= m_resourceValue.equals(property.getResourceValue()); 1012 } 1013 1014 return isEqual; 1015 } 1016 1017 /** 1018 * Checks if this property object is the null property object.<p> 1019 * 1020 * @return true if this property object is the null property object 1021 */ 1022 public boolean isNullProperty() { 1023 1024 return NULL_PROPERTY.equals(this); 1025 } 1026 1027 /** 1028 * Sets the boolean flag to decide if the property definition for this property should be 1029 * created implicitly on any write operation if doesn't exist already.<p> 1030 * 1031 * @param value true, if the property definition for this property should be created implicitly on any write operation 1032 */ 1033 public void setAutoCreatePropertyDefinition(boolean value) { 1034 1035 checkFrozen(); 1036 m_autoCreatePropertyDefinition = value; 1037 } 1038 1039 /** 1040 * Sets the frozen state of the property, if set to <code>true</code> then this property is read only.<p> 1041 * 1042 * If the property is already frozen, then setting the frozen state to <code>true</code> again is allowed, 1043 * but setting the value to <code>false</code> causes a <code>{@link CmsRuntimeException}</code>.<p> 1044 * 1045 * @param frozen the frozen state to set 1046 */ 1047 public void setFrozen(boolean frozen) { 1048 1049 if (!frozen) { 1050 checkFrozen(); 1051 } 1052 m_frozen = frozen; 1053 } 1054 1055 /** 1056 * Sets the name of this property.<p> 1057 * 1058 * @param name the name to set 1059 */ 1060 public void setName(String name) { 1061 1062 checkFrozen(); 1063 m_name = name.trim(); 1064 } 1065 1066 /** 1067 * Sets the path of the resource from which the property was read.<p> 1068 * 1069 * @param originRootPath the root path of the root path from which the property was read 1070 */ 1071 public void setOrigin(String originRootPath) { 1072 1073 checkFrozen(); 1074 m_origin = originRootPath; 1075 } 1076 1077 /** 1078 * Sets the value of this property attached to the resource record.<p> 1079 * 1080 * @param resourceValue the value of this property attached to the resource record 1081 */ 1082 public void setResourceValue(String resourceValue) { 1083 1084 checkFrozen(); 1085 m_resourceValue = resourceValue; 1086 m_resourceValueList = null; 1087 } 1088 1089 /** 1090 * Sets the value of this property attached to the resource record from the given list of Strings.<p> 1091 * 1092 * The value will be created from the individual values of the given list, which are appended 1093 * using the <code>|</code> char as delimiter.<p> 1094 * 1095 * @param valueList the list of value (Strings) to attach to the resource record 1096 */ 1097 public void setResourceValueList(List<String> valueList) { 1098 1099 checkFrozen(); 1100 if (valueList != null) { 1101 m_resourceValueList = new ArrayList<String>(valueList); 1102 m_resourceValueList = Collections.unmodifiableList(m_resourceValueList); 1103 m_resourceValue = createValueFromList(m_resourceValueList); 1104 } else { 1105 m_resourceValueList = null; 1106 m_resourceValue = null; 1107 } 1108 } 1109 1110 /** 1111 * Sets the value of this property attached to the resource record from the given map of Strings.<p> 1112 * 1113 * The value will be created from the individual values of the given map, which are appended 1114 * using the <code>|</code> char as delimiter, the map keys and values are separated by a <code>=</code>.<p> 1115 * 1116 * @param valueMap the map of key/value (Strings) to attach to the resource record 1117 */ 1118 public void setResourceValueMap(Map<String, String> valueMap) { 1119 1120 checkFrozen(); 1121 if (valueMap != null) { 1122 m_resourceValueMap = new HashMap<String, String>(valueMap); 1123 m_resourceValueMap = Collections.unmodifiableMap(m_resourceValueMap); 1124 m_resourceValue = createValueFromMap(m_resourceValueMap); 1125 } else { 1126 m_resourceValueMap = null; 1127 m_resourceValue = null; 1128 } 1129 } 1130 1131 /** 1132 * Sets the value of this property attached to the structure record.<p> 1133 * 1134 * @param structureValue the value of this property attached to the structure record 1135 */ 1136 public void setStructureValue(String structureValue) { 1137 1138 checkFrozen(); 1139 m_structureValue = structureValue; 1140 m_structureValueList = null; 1141 } 1142 1143 /** 1144 * Sets the value of this property attached to the structure record from the given list of Strings.<p> 1145 * 1146 * The value will be created from the individual values of the given list, which are appended 1147 * using the <code>|</code> char as delimiter.<p> 1148 * 1149 * @param valueList the list of value (Strings) to attach to the structure record 1150 */ 1151 public void setStructureValueList(List<String> valueList) { 1152 1153 checkFrozen(); 1154 if (valueList != null) { 1155 m_structureValueList = new ArrayList<String>(valueList); 1156 m_structureValueList = Collections.unmodifiableList(m_structureValueList); 1157 m_structureValue = createValueFromList(m_structureValueList); 1158 } else { 1159 m_structureValueList = null; 1160 m_structureValue = null; 1161 } 1162 } 1163 1164 /** 1165 * Sets the value of this property attached to the structure record from the given map of Strings.<p> 1166 * 1167 * The value will be created from the individual values of the given map, which are appended 1168 * using the <code>|</code> char as delimiter, the map keys and values are separated by a <code>=</code>.<p> 1169 * 1170 * @param valueMap the map of key/value (Strings) to attach to the structure record 1171 */ 1172 public void setStructureValueMap(Map<String, String> valueMap) { 1173 1174 checkFrozen(); 1175 if (valueMap != null) { 1176 m_structureValueMap = new HashMap<String, String>(valueMap); 1177 m_structureValueMap = Collections.unmodifiableMap(m_structureValueMap); 1178 m_structureValue = createValueFromMap(m_structureValueMap); 1179 } else { 1180 m_structureValueMap = null; 1181 m_structureValue = null; 1182 } 1183 } 1184 1185 /** 1186 * Sets the value of this property as either shared or 1187 * individual value.<p> 1188 * 1189 * If the given type equals {@link CmsProperty#TYPE_SHARED} then 1190 * the value is set as a shared (resource) value, otherwise it 1191 * is set as individual (structure) value.<p> 1192 * 1193 * @param value the value to set 1194 * @param type the value type to set 1195 */ 1196 public void setValue(String value, String type) { 1197 1198 checkFrozen(); 1199 setAutoCreatePropertyDefinition(true); 1200 if (TYPE_SHARED.equalsIgnoreCase(type)) { 1201 // set the provided value as shared (resource) value 1202 setResourceValue(value); 1203 } else { 1204 // set the provided value as individual (structure) value 1205 setStructureValue(value); 1206 } 1207 } 1208 1209 /** 1210 * Returns a string representation of this property object.<p> 1211 * 1212 * @see java.lang.Object#toString() 1213 */ 1214 @Override 1215 public String toString() { 1216 1217 StringBuffer strBuf = new StringBuffer(); 1218 1219 strBuf.append("[").append(getClass().getName()).append(": "); 1220 strBuf.append("name: '").append(m_name).append("'"); 1221 strBuf.append(", value: '").append(getValue()).append("'"); 1222 strBuf.append(", structure value: '").append(m_structureValue).append("'"); 1223 strBuf.append(", resource value: '").append(m_resourceValue).append("'"); 1224 strBuf.append(", frozen: ").append(m_frozen); 1225 strBuf.append(", origin: ").append(m_origin); 1226 strBuf.append("]"); 1227 1228 return strBuf.toString(); 1229 } 1230 1231 /** 1232 * Checks if this property is frozen, that is read only.<p> 1233 */ 1234 private void checkFrozen() { 1235 1236 if (m_frozen) { 1237 throw new CmsRuntimeException(Messages.get().container(Messages.ERR_PROPERTY_FROZEN_1, toString())); 1238 } 1239 } 1240 1241 /** 1242 * Returns the list value representation for the given String.<p> 1243 * 1244 * The given value is split along the <code>|</code> char.<p> 1245 * 1246 * @param value the value to create the list representation for 1247 * 1248 * @return the list value representation for the given String 1249 */ 1250 private List<String> createListFromValue(String value) { 1251 1252 if (value == null) { 1253 return null; 1254 } 1255 List<String> result = CmsStringUtil.splitAsList(value, VALUE_LIST_DELIMITER); 1256 if (value.indexOf(VALUE_LIST_DELIMITER_REPLACEMENT) != -1) { 1257 List<String> tempList = new ArrayList<String>(result.size()); 1258 Iterator<String> i = result.iterator(); 1259 while (i.hasNext()) { 1260 String item = i.next(); 1261 tempList.add(rebuildDelimiter(item, VALUE_LIST_DELIMITER, VALUE_LIST_DELIMITER_REPLACEMENT)); 1262 } 1263 result = tempList; 1264 } 1265 1266 return result; 1267 } 1268 1269 /** 1270 * Returns the map value representation for the given String.<p> 1271 * 1272 * The given value is split along the <code>|</code> char, the map keys and values are separated by a <code>=</code>.<p> 1273 * 1274 * @param value the value to create the map representation for 1275 * 1276 * @return the map value representation for the given String 1277 */ 1278 private Map<String, String> createMapFromValue(String value) { 1279 1280 if (value == null) { 1281 return null; 1282 } 1283 List<String> entries = createListFromValue(value); 1284 Iterator<String> i = entries.iterator(); 1285 Map<String, String> result = new HashMap<String, String>(entries.size()); 1286 boolean rebuildDelimiters = false; 1287 if (value.indexOf(VALUE_MAP_DELIMITER_REPLACEMENT) != -1) { 1288 rebuildDelimiters = true; 1289 } 1290 while (i.hasNext()) { 1291 String entry = i.next(); 1292 int index = entry.indexOf(VALUE_MAP_DELIMITER); 1293 if (index != -1) { 1294 String key = entry.substring(0, index); 1295 String val = ""; 1296 if ((index + 1) < entry.length()) { 1297 val = entry.substring(index + 1); 1298 } 1299 if (CmsStringUtil.isNotEmpty(key)) { 1300 if (rebuildDelimiters) { 1301 key = rebuildDelimiter(key, VALUE_MAP_DELIMITER, VALUE_MAP_DELIMITER_REPLACEMENT); 1302 val = rebuildDelimiter(val, VALUE_MAP_DELIMITER, VALUE_MAP_DELIMITER_REPLACEMENT); 1303 } 1304 result.put(key, val); 1305 } 1306 } 1307 } 1308 return result; 1309 } 1310 1311 /** 1312 * Returns the single String value representation for the given value list.<p> 1313 * 1314 * @param valueList the value list to create the single String value for 1315 * 1316 * @return the single String value representation for the given value list 1317 */ 1318 private String createValueFromList(List<String> valueList) { 1319 1320 if (valueList == null) { 1321 return null; 1322 } 1323 StringBuffer result = new StringBuffer(valueList.size() * 32); 1324 Iterator<String> i = valueList.iterator(); 1325 while (i.hasNext()) { 1326 result.append( 1327 replaceDelimiter(i.next().toString(), VALUE_LIST_DELIMITER, VALUE_LIST_DELIMITER_REPLACEMENT)); 1328 if (i.hasNext()) { 1329 result.append(VALUE_LIST_DELIMITER); 1330 } 1331 } 1332 return result.toString(); 1333 } 1334 1335 /** 1336 * Returns the single String value representation for the given value map.<p> 1337 * 1338 * @param valueMap the value map to create the single String value for 1339 * 1340 * @return the single String value representation for the given value map 1341 */ 1342 private String createValueFromMap(Map<String, String> valueMap) { 1343 1344 if (valueMap == null) { 1345 return null; 1346 } 1347 StringBuffer result = new StringBuffer(valueMap.size() * 32); 1348 Iterator<Map.Entry<String, String>> i = valueMap.entrySet().iterator(); 1349 while (i.hasNext()) { 1350 Map.Entry<String, String> entry = i.next(); 1351 String key = entry.getKey(); 1352 String value = entry.getValue(); 1353 key = replaceDelimiter(key, VALUE_LIST_DELIMITER, VALUE_LIST_DELIMITER_REPLACEMENT); 1354 key = replaceDelimiter(key, VALUE_MAP_DELIMITER, VALUE_MAP_DELIMITER_REPLACEMENT); 1355 value = replaceDelimiter(value, VALUE_LIST_DELIMITER, VALUE_LIST_DELIMITER_REPLACEMENT); 1356 value = replaceDelimiter(value, VALUE_MAP_DELIMITER, VALUE_MAP_DELIMITER_REPLACEMENT); 1357 result.append(key); 1358 result.append(VALUE_MAP_DELIMITER); 1359 result.append(value); 1360 if (i.hasNext()) { 1361 result.append(VALUE_LIST_DELIMITER); 1362 } 1363 } 1364 return result.toString(); 1365 } 1366 1367 /** 1368 * Rebuilds the given delimiter character from the replacement string.<p> 1369 * 1370 * @param value the string that is scanned 1371 * @param delimiter the delimiter character to rebuild 1372 * @param delimiterReplacement the replacement string for the delimiter character 1373 * @return the substituted string 1374 */ 1375 private String rebuildDelimiter(String value, char delimiter, String delimiterReplacement) { 1376 1377 return CmsStringUtil.substitute(value, delimiterReplacement, String.valueOf(delimiter)); 1378 } 1379 1380 /** 1381 * Replaces the given delimiter character with the replacement string.<p> 1382 * 1383 * @param value the string that is scanned 1384 * @param delimiter the delimiter character to replace 1385 * @param delimiterReplacement the replacement string for the delimiter character 1386 * @return the substituted string 1387 */ 1388 private String replaceDelimiter(String value, char delimiter, String delimiterReplacement) { 1389 1390 return CmsStringUtil.substitute(value, String.valueOf(delimiter), delimiterReplacement); 1391 } 1392 1393}