001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2009 Sun Microsystems, Inc. 015 * Portions copyright 2011-2016 ForgeRock AS. 016 */ 017package org.forgerock.opendj.ldap.schema; 018 019import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_NAME_FORM_STRUCTURAL_CLASS_NOT_STRUCTURAL1; 020import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR1; 021import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR1; 022import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS1; 023 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.HashSet; 028import java.util.Iterator; 029import java.util.LinkedHashSet; 030import java.util.LinkedList; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034 035import org.forgerock.i18n.LocalizableMessage; 036import org.forgerock.i18n.LocalizableMessageDescriptor.Arg2; 037 038/** 039 * This class defines a data structure for storing and interacting with a name 040 * form, which defines the attribute type(s) that must and/or may be used in the 041 * RDN of an entry with a given structural objectclass. 042 */ 043public final class NameForm extends AbstractSchemaElement { 044 045 /** A fluent API for incrementally constructing name forms. */ 046 public static final class Builder extends SchemaElementBuilder<Builder> { 047 private boolean isObsolete; 048 private final List<String> names = new LinkedList<>(); 049 private String oid; 050 private final Set<String> optionalAttributes = new LinkedHashSet<>(); 051 private final Set<String> requiredAttributes = new LinkedHashSet<>(); 052 private String structuralObjectClassOID; 053 054 Builder(final NameForm nf, final SchemaBuilder builder) { 055 super(builder, nf); 056 this.oid = nf.oid; 057 this.structuralObjectClassOID = nf.structuralClassOID; 058 this.isObsolete = nf.isObsolete; 059 this.names.addAll(nf.names); 060 this.requiredAttributes.addAll(nf.requiredAttributeOIDs); 061 this.optionalAttributes.addAll(nf.optionalAttributeOIDs); 062 } 063 064 Builder(final String oid, final SchemaBuilder builder) { 065 super(builder); 066 oid(oid); 067 } 068 069 /** 070 * Adds this name form to the schema, throwing a 071 * {@code ConflictingSchemaElementException} if there is an existing 072 * name form with the same numeric OID. 073 * 074 * @return The parent schema builder. 075 * @throws ConflictingSchemaElementException 076 * If there is an existing name form with the same numeric 077 * OID. 078 */ 079 public SchemaBuilder addToSchema() { 080 return getSchemaBuilder().addNameForm(new NameForm(this), false); 081 } 082 083 /** 084 * Adds this name form to the schema overwriting any existing name form 085 * with the same numeric OID. 086 * 087 * @return The parent schema builder. 088 */ 089 public SchemaBuilder addToSchemaOverwrite() { 090 return getSchemaBuilder().addNameForm(new NameForm(this), true); 091 } 092 093 /** 094 * Adds this name form to the schema, overwriting any existing name form 095 * with the same numeric OID if the overwrite parameter is set to {@code true}. 096 * 097 * @param overwrite 098 * {@code true} if any name form with the same OID should be overwritten. 099 * @return The parent schema builder. 100 */ 101 SchemaBuilder addToSchema(final boolean overwrite) { 102 return overwrite ? addToSchemaOverwrite() : addToSchema(); 103 } 104 105 @Override 106 public Builder description(final String description) { 107 return description0(description); 108 } 109 110 @Override 111 public Builder extraProperties(final Map<String, List<String>> extraProperties) { 112 return extraProperties0(extraProperties); 113 } 114 115 @Override 116 public Builder extraProperties(final String extensionName, final String... extensionValues) { 117 return extraProperties0(extensionName, extensionValues); 118 } 119 120 /** 121 * Adds the provided user friendly names. 122 * 123 * @param names 124 * The user friendly names. 125 * @return This builder. 126 */ 127 public Builder names(final Collection<String> names) { 128 this.names.addAll(names); 129 return this; 130 } 131 132 /** 133 * Adds the provided user friendly names. 134 * 135 * @param names 136 * The user friendly names. 137 * @return This builder. 138 */ 139 public Builder names(final String... names) { 140 return names(Arrays.asList(names)); 141 } 142 143 /** 144 * Specifies whether this schema element is obsolete. 145 * 146 * @param isObsolete 147 * {@code true} if this schema element is obsolete (default 148 * is {@code false}). 149 * @return This builder. 150 */ 151 public Builder obsolete(final boolean isObsolete) { 152 this.isObsolete = isObsolete; 153 return this; 154 } 155 156 /** 157 * Sets the numeric OID which uniquely identifies this name form. 158 * 159 * @param oid 160 * The numeric OID. 161 * @return This builder. 162 */ 163 public Builder oid(final String oid) { 164 this.oid = oid; 165 return this; 166 } 167 168 /** 169 * Adds the provided optional attributes. 170 * 171 * @param nameOrOIDs 172 * The list of optional attributes. 173 * @return This builder. 174 */ 175 public Builder optionalAttributes(final Collection<String> nameOrOIDs) { 176 this.optionalAttributes.addAll(nameOrOIDs); 177 return this; 178 } 179 180 /** 181 * Adds the provided optional attributes. 182 * 183 * @param nameOrOIDs 184 * The list of optional attributes. 185 * @return This builder. 186 */ 187 public Builder optionalAttributes(final String... nameOrOIDs) { 188 return optionalAttributes(Arrays.asList(nameOrOIDs)); 189 } 190 191 @Override 192 public Builder removeAllExtraProperties() { 193 return removeAllExtraProperties0(); 194 } 195 196 /** 197 * Removes all user friendly names. 198 * 199 * @return This builder. 200 */ 201 public Builder removeAllNames() { 202 this.names.clear(); 203 return this; 204 } 205 206 /** 207 * Removes all optional attributes. 208 * 209 * @return This builder. 210 */ 211 public Builder removeAllOptionalAttributes() { 212 this.optionalAttributes.clear(); 213 return this; 214 } 215 216 /** 217 * Removes all required attributes. 218 * 219 * @return This builder. 220 */ 221 public Builder removeAllRequiredAttributes() { 222 this.requiredAttributes.clear(); 223 return this; 224 } 225 226 @Override 227 public Builder removeExtraProperty(final String extensionName, 228 final String... extensionValues) { 229 return removeExtraProperty0(extensionName, extensionValues); 230 } 231 232 /** 233 * Removes the provided user friendly name. 234 * 235 * @param name 236 * The user friendly name to be removed. 237 * @return This builder. 238 */ 239 public Builder removeName(final String name) { 240 names.remove(name); 241 return this; 242 } 243 244 /** 245 * Removes the specified optional attribute. 246 * 247 * @param nameOrOID 248 * The optional attribute to be removed. 249 * @return This builder. 250 */ 251 public Builder removeOptionalAttribute(final String nameOrOID) { 252 this.optionalAttributes.remove(nameOrOID); 253 return this; 254 } 255 256 /** 257 * Removes the specified required attribute. 258 * 259 * @param nameOrOID 260 * The required attribute to be removed. 261 * @return This builder. 262 */ 263 public Builder removeRequiredAttribute(final String nameOrOID) { 264 this.requiredAttributes.remove(nameOrOID); 265 return this; 266 } 267 268 /** 269 * Adds the provided required attributes. 270 * 271 * @param nameOrOIDs 272 * The list of required attributes. 273 * @return This builder. 274 */ 275 public Builder requiredAttributes(final Collection<String> nameOrOIDs) { 276 this.requiredAttributes.addAll(nameOrOIDs); 277 return this; 278 } 279 280 /** 281 * Adds the provided required attributes. 282 * 283 * @param nameOrOIDs 284 * The list of required attributes. 285 * @return This builder. 286 */ 287 public Builder requiredAttributes(final String... nameOrOIDs) { 288 return requiredAttributes(Arrays.asList(nameOrOIDs)); 289 } 290 291 /** 292 * Sets the structural object class. 293 * 294 * @param nameOrOID 295 * The structural object class. 296 * @return This builder. 297 */ 298 public Builder structuralObjectClassOID(final String nameOrOID) { 299 this.structuralObjectClassOID = nameOrOID; 300 return this; 301 } 302 303 @Override 304 Builder getThis() { 305 return this; 306 } 307 } 308 309 /** Indicates whether this definition is declared "obsolete". */ 310 private final boolean isObsolete; 311 312 /** The set of user defined names for this definition. */ 313 private final List<String> names; 314 315 /** The OID that may be used to reference this definition. */ 316 private final String oid; 317 318 /** The set of optional attribute types for this name form. */ 319 private final Set<String> optionalAttributeOIDs; 320 private Set<AttributeType> optionalAttributes = Collections.emptySet(); 321 322 /** The set of required attribute types for this name form. */ 323 private final Set<String> requiredAttributeOIDs; 324 private Set<AttributeType> requiredAttributes = Collections.emptySet(); 325 326 /** The reference to the structural objectclass for this name form. */ 327 private ObjectClass structuralClass; 328 private final String structuralClassOID; 329 330 private NameForm(final Builder builder) { 331 super(builder); 332 333 // Checks for required attributes. 334 if (builder.oid == null || builder.oid.isEmpty()) { 335 throw new IllegalArgumentException("An OID must be specified."); 336 } 337 if (builder.structuralObjectClassOID == null || builder.structuralObjectClassOID.isEmpty()) { 338 throw new IllegalArgumentException("A structural class OID must be specified."); 339 } 340 if (builder.requiredAttributes == null || builder.requiredAttributes.isEmpty()) { 341 throw new IllegalArgumentException("Required attribute must be specified."); 342 } 343 344 oid = builder.oid; 345 structuralClassOID = builder.structuralObjectClassOID; 346 names = SchemaUtils.unmodifiableCopyOfList(builder.names); 347 requiredAttributeOIDs = SchemaUtils.unmodifiableCopyOfSet(builder.requiredAttributes); 348 optionalAttributeOIDs = SchemaUtils.unmodifiableCopyOfSet(builder.optionalAttributes); 349 isObsolete = builder.isObsolete; 350 } 351 352 /** 353 * Returns {@code true} if the provided object is a name form having the 354 * same numeric OID as this name form. 355 * 356 * @param o 357 * The object to be compared. 358 * @return {@code true} if the provided object is a name form having the 359 * same numeric OID as this name form. 360 */ 361 @Override 362 public boolean equals(final Object o) { 363 if (this == o) { 364 return true; 365 } else if (o instanceof NameForm) { 366 final NameForm other = (NameForm) o; 367 return oid.equals(other.oid); 368 } else { 369 return false; 370 } 371 } 372 373 /** 374 * Returns the name or numeric OID of this name form. If it has one or more 375 * names, then the primary name will be returned. If it does not have any 376 * names, then the numeric OID will be returned. 377 * 378 * @return The name or numeric OID of this name form. 379 */ 380 public String getNameOrOID() { 381 if (names.isEmpty()) { 382 return oid; 383 } 384 return names.get(0); 385 } 386 387 /** 388 * Returns an unmodifiable list containing the user-friendly names that may 389 * be used to reference this name form. 390 * 391 * @return An unmodifiable list containing the user-friendly names that may 392 * be used to reference this name form. 393 */ 394 public List<String> getNames() { 395 return names; 396 } 397 398 /** 399 * Returns the numeric OID of this name form. 400 * 401 * @return The numeric OID of this name form. 402 */ 403 public String getOID() { 404 return oid; 405 } 406 407 /** 408 * Returns an unmodifiable set containing the optional attributes of this 409 * name form. 410 * 411 * @return An unmodifiable set containing the optional attributes of this 412 * name form. 413 */ 414 public Set<AttributeType> getOptionalAttributes() { 415 return optionalAttributes; 416 } 417 418 /** 419 * Returns an unmodifiable set containing the required attributes of this 420 * name form. 421 * 422 * @return An unmodifiable set containing the required attributes of this 423 * name form. 424 */ 425 public Set<AttributeType> getRequiredAttributes() { 426 return requiredAttributes; 427 } 428 429 /** 430 * Returns the structural objectclass of this name form. 431 * 432 * @return The structural objectclass of this name form. 433 */ 434 public ObjectClass getStructuralClass() { 435 return structuralClass; 436 } 437 438 /** 439 * Returns the hash code for this name form. It will be calculated as the 440 * hash code of the numeric OID. 441 * 442 * @return The hash code for this name form. 443 */ 444 @Override 445 public int hashCode() { 446 return oid.hashCode(); 447 } 448 449 /** 450 * Returns {@code true} if this name form has the specified user-friendly 451 * name. 452 * 453 * @param name 454 * The name. 455 * @return {@code true} if this name form has the specified user-friendly 456 * name. 457 */ 458 public boolean hasName(final String name) { 459 for (final String n : names) { 460 if (n.equalsIgnoreCase(name)) { 461 return true; 462 } 463 } 464 return false; 465 } 466 467 /** 468 * Returns {@code true} if this name form has the specified user-friendly 469 * name or numeric OID. 470 * 471 * @param nameOrOID 472 * The name or numeric OID. 473 * @return {@code true} if this name form has the specified user-friendly 474 * name or numeric OID. 475 */ 476 public boolean hasNameOrOID(final String nameOrOID) { 477 return hasName(nameOrOID) || getOID().equals(nameOrOID); 478 } 479 480 /** 481 * Returns {@code true} if this name form is "obsolete". 482 * 483 * @return {@code true} if this name form is "obsolete". 484 */ 485 public boolean isObsolete() { 486 return isObsolete; 487 } 488 489 /** 490 * Returns {@code true} if the provided attribute type is included in the 491 * list of optional attributes for this name form. 492 * 493 * @param attributeType 494 * The attribute type. 495 * @return {@code true} if the provided attribute type is included in the 496 * list of optional attributes for this name form. 497 */ 498 public boolean isOptional(final AttributeType attributeType) { 499 return optionalAttributes.contains(attributeType); 500 } 501 502 /** 503 * Returns {@code true} if the provided attribute type is included in the 504 * list of required attributes for this name form. 505 * 506 * @param attributeType 507 * The attribute type. 508 * @return {@code true} if the provided attribute type is included in the 509 * list of required attributes for this name form. 510 */ 511 public boolean isRequired(final AttributeType attributeType) { 512 return requiredAttributes.contains(attributeType); 513 } 514 515 /** 516 * Returns {@code true} if the provided attribute type is included in the 517 * list of optional or required attributes for this name form. 518 * 519 * @param attributeType 520 * The attribute type. 521 * @return {@code true} if the provided attribute type is included in the 522 * list of optional or required attributes for this name form. 523 */ 524 public boolean isRequiredOrOptional(final AttributeType attributeType) { 525 return isRequired(attributeType) || isOptional(attributeType); 526 } 527 528 @Override 529 void toStringContent(final StringBuilder buffer) { 530 buffer.append(oid); 531 532 if (!names.isEmpty()) { 533 final Iterator<String> iterator = names.iterator(); 534 final String firstName = iterator.next(); 535 if (iterator.hasNext()) { 536 buffer.append(" NAME ( '"); 537 buffer.append(firstName); 538 while (iterator.hasNext()) { 539 buffer.append("' '"); 540 buffer.append(iterator.next()); 541 } 542 buffer.append("' )"); 543 } else { 544 buffer.append(" NAME '"); 545 buffer.append(firstName); 546 buffer.append("'"); 547 } 548 } 549 550 appendDescription(buffer); 551 552 if (isObsolete) { 553 buffer.append(" OBSOLETE"); 554 } 555 556 buffer.append(" OC "); 557 buffer.append(structuralClassOID); 558 559 if (!requiredAttributeOIDs.isEmpty()) { 560 final Iterator<String> iterator = requiredAttributeOIDs.iterator(); 561 final String firstName = iterator.next(); 562 if (iterator.hasNext()) { 563 buffer.append(" MUST ( "); 564 buffer.append(firstName); 565 while (iterator.hasNext()) { 566 buffer.append(" $ "); 567 buffer.append(iterator.next()); 568 } 569 buffer.append(" )"); 570 } else { 571 buffer.append(" MUST "); 572 buffer.append(firstName); 573 } 574 } 575 576 if (!optionalAttributeOIDs.isEmpty()) { 577 final Iterator<String> iterator = optionalAttributeOIDs.iterator(); 578 final String firstName = iterator.next(); 579 if (iterator.hasNext()) { 580 buffer.append(" MAY ( "); 581 buffer.append(firstName); 582 while (iterator.hasNext()) { 583 buffer.append(" $ "); 584 buffer.append(iterator.next()); 585 } 586 buffer.append(" )"); 587 } else { 588 buffer.append(" MAY "); 589 buffer.append(firstName); 590 } 591 } 592 } 593 594 void validate(final Schema schema) throws SchemaException { 595 try { 596 structuralClass = schema.getObjectClass(structuralClassOID); 597 } catch (final UnknownSchemaElementException e) { 598 final LocalizableMessage message = 599 ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS1.get(getNameOrOID(), 600 structuralClassOID); 601 throw new SchemaException(message, e); 602 } 603 if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL) { 604 // This is bad because the associated structural class type is not structural. 605 final LocalizableMessage message = 606 ERR_ATTR_SYNTAX_NAME_FORM_STRUCTURAL_CLASS_NOT_STRUCTURAL1.get(getNameOrOID(), 607 structuralClass.getNameOrOID(), structuralClass.getObjectClassType()); 608 throw new SchemaException(message); 609 } 610 611 requiredAttributes = 612 getAttributeTypes(schema, requiredAttributeOIDs, ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR1); 613 614 if (!optionalAttributeOIDs.isEmpty()) { 615 optionalAttributes = 616 getAttributeTypes(schema, optionalAttributeOIDs, ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR1); 617 } 618 619 optionalAttributes = Collections.unmodifiableSet(optionalAttributes); 620 requiredAttributes = Collections.unmodifiableSet(requiredAttributes); 621 } 622 623 private Set<AttributeType> getAttributeTypes(final Schema schema, Set<String> oids, Arg2<Object, Object> errorMsg) 624 throws SchemaException { 625 Set<AttributeType> attrTypes = new HashSet<>(oids.size()); 626 for (final String oid : oids) { 627 try { 628 attrTypes.add(schema.getAttributeType(oid)); 629 } catch (final UnknownSchemaElementException e) { 630 // This isn't good because it means that the name form requires 631 // an attribute type that we don't know anything about. 632 throw new SchemaException(errorMsg.get(getNameOrOID(), oid), e); 633 } 634 } 635 return attrTypes; 636 } 637}