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