001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.types; 028 029 import static org.opends.server.util.ServerConstants.*; 030 import static org.opends.server.util.StaticUtils.toLowerCase; 031 import static org.opends.server.util.Validator.*; 032 033 import java.util.Collection; 034 import java.util.Collections; 035 import java.util.Iterator; 036 import java.util.LinkedList; 037 import java.util.LinkedHashMap; 038 import java.util.List; 039 import java.util.Map; 040 041 042 043 /** 044 * An abstract base class for LDAP schema definitions which contain an 045 * OID, optional names, description, an obsolete flag, and an optional 046 * set of extra properties. 047 * <p> 048 * This class defines common properties and behaviour of the various 049 * types of schema definitions (e.g. object class definitions, and 050 * attribute type definitions). 051 * <p> 052 * Any methods which accesses the set of names associated with this 053 * definition, will retrieve the primary name as the first name, 054 * regardless of whether or not it was contained in the original set 055 * of <code>names</code> passed to the constructor. 056 * <p> 057 * Where ordered sets of names, or extra properties are provided, the 058 * ordering will be preserved when the associated fields are accessed 059 * via their getters or via the {@link #toString()} methods. 060 * <p> 061 * Note that these schema elements are not completely immutable, as 062 * the set of extra properties for the schema element may be altered 063 * after the element is created. Among other things, this allows the 064 * associated schema file to be edited so that an element created over 065 * protocol may be associated with a particular schema file. 066 */ 067 @org.opends.server.types.PublicAPI( 068 stability=org.opends.server.types.StabilityLevel.VOLATILE, 069 mayInstantiate=false, 070 mayExtend=false, 071 mayInvoke=true) 072 public abstract class CommonSchemaElements { 073 074 // Indicates whether this definition is declared "obsolete". 075 private final boolean isObsolete; 076 077 // The hash code for this definition. 078 private final int hashCode; 079 080 // The set of additional name-value pairs associated with this 081 // definition. 082 private final Map<String, List<String>> extraProperties; 083 084 // The set of names for this definition, in a mapping between 085 // the all-lowercase form and the user-defined form. 086 private final Map<String, String> names; 087 088 // The description for this definition. 089 private final String description; 090 091 // The OID that may be used to reference this definition. 092 private final String oid; 093 094 // The primary name to use for this definition. 095 private final String primaryName; 096 097 // The lower case name for this definition. 098 private final String lowerName; 099 100 101 102 /** 103 * Creates a new definition with the provided information. 104 * <p> 105 * If no <code>primaryName</code> is specified, but a set of 106 * <code>names</code> is specified, then the first name retrieved 107 * from the set of <code>names</code> will be used as the primary 108 * name. 109 * 110 * @param primaryName 111 * The primary name for this definition, or 112 * <code>null</code> if there is no primary name. 113 * @param names 114 * The full set of names for this definition, or 115 * <code>null</code> if there are no names. 116 * @param oid 117 * The OID for this definition (must not be 118 * <code>null</code>). 119 * @param description 120 * The description for the definition, or <code>null</code> 121 * if there is no description. 122 * @param isObsolete 123 * Indicates whether this definition is declared 124 * "obsolete". 125 * @param extraProperties 126 * A set of extra properties for this definition, or 127 * <code>null</code> if there are no extra properties. 128 * @throws NullPointerException 129 * If the provided OID was <code>null</code>. 130 */ 131 protected CommonSchemaElements(String primaryName, 132 Collection<String> names, String oid, String description, 133 boolean isObsolete, Map<String, List<String>> extraProperties) 134 throws NullPointerException { 135 136 137 // Make sure mandatory parameters are specified. 138 if (oid == null) { 139 throw new NullPointerException( 140 "No oid specified in constructor"); 141 } 142 143 this.oid = oid; 144 this.description = description; 145 this.isObsolete = isObsolete; 146 147 hashCode = oid.hashCode(); 148 149 // Make sure we have a primary name if possible. 150 if (primaryName == null) { 151 if (names != null && !names.isEmpty()) { 152 this.primaryName = names.iterator().next(); 153 } else { 154 this.primaryName = null; 155 } 156 } else { 157 this.primaryName = primaryName; 158 } 159 this.lowerName = toLowerCase(primaryName); 160 161 // Construct the normalized attribute name mapping. 162 if (names != null) { 163 this.names = new LinkedHashMap<String, String>(names.size()); 164 165 // Make sure the primary name is first (never null). 166 this.names.put(lowerName, this.primaryName); 167 168 // Add the remaining names in the order specified. 169 for (String name : names) { 170 this.names.put(toLowerCase(name), name); 171 } 172 } else if (this.primaryName != null) { 173 this.names = Collections.singletonMap(lowerName, 174 this.primaryName); 175 } else { 176 this.names = Collections.emptyMap(); 177 } 178 179 // FIXME: should really be a deep-copy. 180 if (extraProperties != null) { 181 this.extraProperties = new LinkedHashMap<String, List<String>>( 182 extraProperties); 183 } else { 184 this.extraProperties = Collections.emptyMap(); 185 } 186 } 187 188 189 190 /** 191 * Retrieves the primary name for this schema definition. 192 * 193 * @return The primary name for this schema definition, or 194 * <code>null</code> if there is no primary name. 195 */ 196 public final String getPrimaryName() { 197 198 return primaryName; 199 } 200 201 202 203 /** 204 * Retrieve the normalized primary name for this schema definition. 205 * 206 * @return Returns the normalized primary name for this attribute 207 * type, or <code>null</code> if there is no primary name. 208 */ 209 public final String getNormalizedPrimaryName() { 210 211 return lowerName; 212 } 213 214 215 216 /** 217 * Retrieves an iterable over the set of normalized names that may 218 * be used to reference this schema definition. The normalized form 219 * of an attribute name is defined as the user-defined name 220 * converted to lower-case. 221 * 222 * @return Returns an iterable over the set of normalized names that 223 * may be used to reference this schema definition. 224 */ 225 public final Iterable<String> getNormalizedNames() { 226 227 return names.keySet(); 228 } 229 230 231 232 /** 233 * Retrieves an iterable over the set of user-defined names that may 234 * be used to reference this schema definition. 235 * 236 * @return Returns an iterable over the set of user-defined names 237 * that may be used to reference this schema definition. 238 */ 239 public final Iterable<String> getUserDefinedNames() { 240 241 return names.values(); 242 } 243 244 245 246 /** 247 * Indicates whether this schema definition has the specified name. 248 * 249 * @param lowerName 250 * The lowercase name for which to make the determination. 251 * @return <code>true</code> if the specified name is assigned to 252 * this schema definition, or <code>false</code> if not. 253 */ 254 public final boolean hasName(String lowerName) { 255 256 return names.containsKey(lowerName); 257 } 258 259 260 261 /** 262 * Retrieves the OID for this schema definition. 263 * 264 * @return The OID for this schema definition. 265 */ 266 public final String getOID() { 267 268 return oid; 269 } 270 271 272 273 /** 274 * Retrieves the name or OID for this schema definition. If it has 275 * one or more names, then the primary name will be returned. If it 276 * does not have any names, then the OID will be returned. 277 * 278 * @return The name or OID for this schema definition. 279 */ 280 public final String getNameOrOID() { 281 282 if (primaryName != null) { 283 return primaryName; 284 } else { 285 // Guaranteed not to be null. 286 return oid; 287 } 288 } 289 290 291 292 /** 293 * Indicates whether this schema definition has the specified name 294 * or OID. 295 * 296 * @param lowerValue 297 * The lowercase value for which to make the determination. 298 * @return <code>true</code> if the provided value matches the OID 299 * or one of the names assigned to this schema definition, 300 * or <code>false</code> if not. 301 */ 302 public final boolean hasNameOrOID(String lowerValue) { 303 304 if (names.containsKey(lowerValue)) { 305 return true; 306 } 307 308 return oid.equals(lowerValue); 309 } 310 311 312 313 /** 314 * Retrieves the name of the schema file that contains the 315 * definition for this schema definition. 316 * 317 * @return The name of the schema file that contains the definition 318 * for this schema definition, or <code>null</code> if it 319 * is not known or if it is not stored in any schema file. 320 */ 321 public final String getSchemaFile() { 322 323 List<String> values = extraProperties 324 .get(SCHEMA_PROPERTY_FILENAME); 325 if (values != null && !values.isEmpty()) { 326 return values.get(0); 327 } 328 329 return null; 330 } 331 332 333 334 /** 335 * Specifies the name of the schema file that contains the 336 * definition for this schema element. If a schema file is already 337 * defined in the set of extra properties, then it will be 338 * overwritten. If the provided schema file value is {@code null}, 339 * then any existing schema file definition will be removed. 340 * 341 * @param schemaFile The name of the schema file that contains the 342 * definition for this schema element. 343 */ 344 public final void setSchemaFile(String schemaFile) { 345 346 setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile); 347 } 348 349 350 351 /** 352 * Retrieves the description for this schema definition. 353 * 354 * @return The description for this schema definition, or 355 * <code>null</code> if there is no description. 356 */ 357 public final String getDescription() { 358 359 return description; 360 } 361 362 363 364 /** 365 * Indicates whether this schema definition is declared "obsolete". 366 * 367 * @return <code>true</code> if this schema definition is declared 368 * "obsolete", or <code>false</code> if not. 369 */ 370 public final boolean isObsolete() { 371 372 return isObsolete; 373 } 374 375 376 377 /** 378 * Retrieves an iterable over the names of "extra" properties 379 * associated with this schema definition. 380 * 381 * @return Returns an iterable over the names of "extra" properties 382 * associated with this schema definition. 383 */ 384 public final Iterable<String> getExtraPropertyNames() { 385 386 return extraProperties.keySet(); 387 } 388 389 390 391 /** 392 * Retrieves an iterable over the value(s) of the specified "extra" 393 * property for this schema definition. 394 * 395 * @param name 396 * The name of the "extra" property for which to retrieve 397 * the value(s). 398 * @return Returns an iterable over the value(s) of the specified 399 * "extra" property for this schema definition, or 400 * <code>null</code> if no such property is defined. 401 */ 402 public final Iterable<String> getExtraProperty(String name) { 403 404 return extraProperties.get(name); 405 } 406 407 408 409 /** 410 * Sets the value for an "extra" property for this schema element. 411 * If a property already exists with the specified name, then it 412 * will be overwritten. If the value is {@code null}, then any 413 * existing property with the given name will be removed. 414 * 415 * @param name The name for the "extra" property. It must not be 416 * {@code null}. 417 * @param value The value for the "extra" property. If it is 418 * {@code null}, then any existing definition will be 419 * removed. 420 */ 421 public final void setExtraProperty(String name, String value) { 422 423 ensureNotNull(name); 424 425 if (value == null) 426 { 427 extraProperties.remove(name); 428 } 429 else 430 { 431 LinkedList<String> values = new LinkedList<String>(); 432 values.add(value); 433 434 extraProperties.put(name, values); 435 } 436 } 437 438 439 440 /** 441 * Sets the values for an "extra" property for this schema element. 442 * If a property already exists with the specified name, then it 443 * will be overwritten. If the set of values is {@code null} or 444 * empty, then any existing property with the given name will be 445 * removed. 446 * 447 * @param name The name for the "extra" property. It must not 448 * be {@code null}. 449 * @param values The set of values for the "extra" property. If 450 * it is {@code null} or empty, then any existing 451 * definition will be removed. 452 */ 453 public final void setExtraProperty(String name, 454 List<String> values) { 455 456 ensureNotNull(name); 457 458 if ((values == null) || values.isEmpty()) 459 { 460 extraProperties.remove(name); 461 } 462 else 463 { 464 LinkedList<String> valuesCopy = new LinkedList<String>(values); 465 extraProperties.put(name, valuesCopy); 466 } 467 } 468 469 470 471 /** 472 * Indicates whether the provided object is equal to this attribute 473 * type. The object will be considered equal if it is an attribute 474 * type with the same OID as the current type. 475 * 476 * @param o 477 * The object for which to make the determination. 478 * @return <code>true</code> if the provided object is equal to 479 * this schema definition, or <code>false</code> if not. 480 */ 481 public final boolean equals(Object o) { 482 483 if (this == o) { 484 return true; 485 } 486 487 if (o instanceof CommonSchemaElements) { 488 CommonSchemaElements other = (CommonSchemaElements) o; 489 return oid.equals(other.oid); 490 } 491 492 return false; 493 } 494 495 496 497 /** 498 * Retrieves the hash code for this schema definition. It will be 499 * based on the sum of the bytes of the OID. 500 * 501 * @return The hash code for this schema definition. 502 */ 503 public final int hashCode() { 504 505 return hashCode; 506 } 507 508 509 510 /** 511 * Retrieves the string representation of this schema definition in 512 * the form specified in RFC 2252. 513 * 514 * @return The string representation of this schema definition in 515 * the form specified in RFC 2252. 516 */ 517 public final String toString() { 518 519 StringBuilder buffer = new StringBuilder(); 520 toString(buffer, true); 521 return buffer.toString(); 522 } 523 524 525 526 /** 527 * Appends a string representation of this schema definition in the 528 * form specified in RFC 2252 to the provided buffer. 529 * 530 * @param buffer 531 * The buffer to which the information should be appended. 532 * @param includeFileElement 533 * Indicates whether to include an "extra" property that 534 * specifies the path to the schema file from which this 535 * schema definition was loaded. 536 */ 537 public final void toString(StringBuilder buffer, 538 boolean includeFileElement) { 539 540 buffer.append("( "); 541 buffer.append(oid); 542 543 if (!names.isEmpty()) { 544 Iterator<String> iterator = names.values().iterator(); 545 546 String firstName = iterator.next(); 547 if (iterator.hasNext()) { 548 buffer.append(" NAME ( '"); 549 buffer.append(firstName); 550 551 while (iterator.hasNext()) { 552 buffer.append("' '"); 553 buffer.append(iterator.next()); 554 } 555 556 buffer.append("' )"); 557 } else { 558 buffer.append(" NAME '"); 559 buffer.append(firstName); 560 buffer.append("'"); 561 } 562 } 563 564 if ((description != null) && (description.length() > 0)) { 565 buffer.append(" DESC '"); 566 buffer.append(description); 567 buffer.append("'"); 568 } 569 570 if (isObsolete) { 571 buffer.append(" OBSOLETE"); 572 } 573 574 // Delegate remaining string output to sub-class. 575 toStringContent(buffer); 576 577 if (!extraProperties.isEmpty()) { 578 for (Map.Entry<String, List<String>> e : extraProperties 579 .entrySet()) { 580 581 String property = e.getKey(); 582 if (!includeFileElement 583 && property.equals(SCHEMA_PROPERTY_FILENAME)) { 584 // Don't include the schema file if it was not requested. 585 continue; 586 } 587 588 List<String> valueList = e.getValue(); 589 590 buffer.append(" "); 591 buffer.append(property); 592 593 if (valueList.size() == 1) { 594 buffer.append(" '"); 595 buffer.append(valueList.get(0)); 596 buffer.append("'"); 597 } else { 598 buffer.append(" ( "); 599 600 for (String value : valueList) { 601 buffer.append("'"); 602 buffer.append(value); 603 buffer.append("' "); 604 } 605 606 buffer.append(")"); 607 } 608 } 609 } 610 611 buffer.append(" )"); 612 } 613 614 615 616 /** 617 * Appends a string representation of this schema definition's 618 * non-generic properties to the provided buffer. 619 * 620 * @param buffer 621 * The buffer to which the information should be appended. 622 */ 623 protected abstract void toStringContent(StringBuilder buffer); 624 }