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