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