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 2015-2016 ForgeRock AS. 016 */ 017package org.forgerock.opendj.ldap.schema; 018 019import static org.forgerock.opendj.ldap.schema.SchemaConstants.TOP_OBJECTCLASS_OID; 020 021import java.util.Collection; 022import java.util.Collections; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.LinkedHashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import org.forgerock.i18n.LocalizableMessage; 032 033import com.forgerock.opendj.util.StaticUtils; 034 035import static java.util.Arrays.*; 036import static java.util.Collections.*; 037import static org.forgerock.opendj.ldap.schema.ObjectClassType.*; 038import static org.forgerock.opendj.ldap.schema.SchemaConstants.*; 039import static org.forgerock.opendj.ldap.schema.SchemaUtils.*; 040import static com.forgerock.opendj.ldap.CoreMessages.*; 041 042/** 043 * This class defines a data structure for storing and interacting with an 044 * objectclass, which contains a collection of attributes that must and/or may 045 * be present in an entry with that objectclass. 046 * <p> 047 * Where ordered sets of names, attribute types, or extra properties are 048 * provided, the ordering will be preserved when the associated fields are 049 * accessed via their getters or via the {@link #toString()} methods. 050 */ 051public final class ObjectClass extends AbstractSchemaElement { 052 053 /** A fluent API for incrementally constructing object classes. */ 054 public static final class Builder extends SchemaElementBuilder<Builder> { 055 private boolean isObsolete; 056 private final List<String> names = new LinkedList<>(); 057 private String oid; 058 private final Set<String> optionalAttributes = new LinkedHashSet<>(); 059 private final Set<String> requiredAttributes = new LinkedHashSet<>(); 060 private final Set<String> superiorClasses = new LinkedHashSet<>(); 061 private ObjectClassType type; 062 063 Builder(final ObjectClass oc, final SchemaBuilder builder) { 064 super(builder, oc); 065 this.oid = oc.oid; 066 this.names.addAll(oc.names); 067 this.isObsolete = oc.isObsolete; 068 this.type = oc.objectClassType; 069 this.superiorClasses.addAll(oc.superiorClassOIDs); 070 this.requiredAttributes.addAll(oc.requiredAttributeOIDs); 071 // Don't copy optional attributes for extensibleObject because they will 072 // prevent attribute types from being removed from the schema. 073 // The optional attributes will be refreshed during validation. 074 if (!oc.isExtensible()) { 075 this.optionalAttributes.addAll(oc.optionalAttributeOIDs); 076 } 077 } 078 079 Builder(final String oid, final SchemaBuilder builder) { 080 super(builder); 081 this.oid = oid; 082 } 083 084 /** 085 * Adds this object class to the schema, throwing a 086 * {@code ConflictingSchemaElementException} if there is an existing 087 * object class with the same numeric OID. 088 * 089 * @return The parent schema builder. 090 * @throws ConflictingSchemaElementException 091 * If there is an existing object class with the same numeric 092 * OID. 093 */ 094 public SchemaBuilder addToSchema() { 095 return getSchemaBuilder().addObjectClass(new ObjectClass(this), false); 096 } 097 098 /** 099 * Adds this object class to the schema overwriting any existing object class 100 * with the same numeric OID. 101 * 102 * @return The parent schema builder. 103 */ 104 public SchemaBuilder addToSchemaOverwrite() { 105 return getSchemaBuilder().addObjectClass(new ObjectClass(this), true); 106 } 107 108 /** 109 * Adds this object class to the schema, overwriting any existing object class 110 * with the same numeric OID if the overwrite parameter is set to {@code true}. 111 * 112 * @param overwrite 113 * {@code true} if any object class with the same OID should be overwritten. 114 * @return The parent schema builder. 115 */ 116 SchemaBuilder addToSchema(final boolean overwrite) { 117 return overwrite ? addToSchemaOverwrite() : addToSchema(); 118 } 119 120 @Override 121 public Builder description(final String description) { 122 return description0(description); 123 } 124 125 @Override 126 public Builder extraProperties(final Map<String, List<String>> extraProperties) { 127 return extraProperties0(extraProperties); 128 } 129 130 @Override 131 public Builder extraProperties(final String extensionName, final String... extensionValues) { 132 return extraProperties0(extensionName, extensionValues); 133 } 134 135 @Override 136 Builder getThis() { 137 return this; 138 } 139 140 /** 141 * Adds the provided user friendly names. 142 * 143 * @param names 144 * The user friendly names. 145 * @return This builder. 146 */ 147 public Builder names(final Collection<String> names) { 148 this.names.addAll(names); 149 return this; 150 } 151 152 /** 153 * Adds the provided user friendly names. 154 * 155 * @param names 156 * The user friendly names. 157 * @return This builder. 158 */ 159 public Builder names(final String... names) { 160 return names(asList(names)); 161 } 162 163 /** 164 * Specifies whether this schema element is obsolete. 165 * 166 * @param isObsolete 167 * {@code true} if this schema element is obsolete 168 * (default is {@code false}). 169 * @return This builder. 170 */ 171 public Builder obsolete(final boolean isObsolete) { 172 this.isObsolete = isObsolete; 173 return this; 174 } 175 176 /** 177 * Sets the numeric OID which uniquely identifies this object class. 178 * 179 * @param oid 180 * The numeric OID. 181 * @return This builder. 182 */ 183 public Builder oid(final String oid) { 184 this.oid = oid; 185 return this; 186 } 187 188 /** 189 * Adds the provided optional attributes. 190 * 191 * @param attributeNamesOrOIDs 192 * The list of optional attribute names or OIDs. 193 * @return This builder. 194 */ 195 public Builder optionalAttributes(final Collection<String> attributeNamesOrOIDs) { 196 this.optionalAttributes.addAll(attributeNamesOrOIDs); 197 return this; 198 } 199 200 /** 201 * Adds the provided optional attributes. 202 * 203 * @param attributeNamesOrOIDs 204 * The list of optional attribute names or OIDs. 205 * @return This builder. 206 */ 207 public Builder optionalAttributes(final String... attributeNamesOrOIDs) { 208 this.optionalAttributes.addAll(asList(attributeNamesOrOIDs)); 209 return this; 210 } 211 212 @Override 213 public Builder removeAllExtraProperties() { 214 return removeAllExtraProperties0(); 215 } 216 217 @Override 218 public Builder removeExtraProperty(final String extensionName, final String... extensionValues) { 219 return removeExtraProperty0(extensionName, extensionValues); 220 } 221 222 /** 223 * Removes all user defined names. 224 * 225 * @return This builder. 226 */ 227 public Builder removeAllNames() { 228 this.names.clear(); 229 return this; 230 } 231 232 /** 233 * Removes all optional attributes. 234 * 235 * @return This builder. 236 */ 237 public Builder removeAllOptionalAttributes() { 238 this.optionalAttributes.clear(); 239 return this; 240 } 241 242 /** 243 * Removes all required attributes. 244 * 245 * @return This builder. 246 */ 247 public Builder removeAllRequiredAttributes() { 248 this.requiredAttributes.clear(); 249 return this; 250 } 251 252 /** 253 * Removes all superior object class. 254 * 255 * @return This builder. 256 */ 257 public Builder removeAllSuperiorObjectClass() { 258 this.superiorClasses.clear(); 259 return this; 260 } 261 262 /** 263 * Removes the provided user defined name. 264 * 265 * @param name 266 * The user defined name to be removed. 267 * @return This builder. 268 */ 269 public Builder removeName(String name) { 270 this.names.remove(name); 271 return this; 272 } 273 274 /** 275 * Removes the provided optional attribute. 276 * 277 * @param attributeNameOrOID 278 * The optional attribute name or OID to be removed. 279 * @return This builder. 280 */ 281 public Builder removeOptionalAttribute(String attributeNameOrOID) { 282 this.optionalAttributes.remove(attributeNameOrOID); 283 return this; 284 } 285 286 /** 287 * Removes the provided required attribute. 288 * 289 * @param attributeNameOrOID 290 * The provided required attribute name or OID to be removed. 291 * @return This builder. 292 */ 293 public Builder removeRequiredAttribute(String attributeNameOrOID) { 294 this.requiredAttributes.remove(attributeNameOrOID); 295 return this; 296 } 297 298 /** 299 * Removes the provided superior object class. 300 * 301 * @param objectClassNameOrOID 302 * The superior object class name or OID to be removed. 303 * @return This builder. 304 */ 305 public Builder removeSuperiorObjectClass(String objectClassNameOrOID) { 306 this.superiorClasses.remove(objectClassNameOrOID); 307 return this; 308 } 309 310 /** 311 * Adds the provided required attributes. 312 * 313 * @param attributeNamesOrOIDs 314 * The list of required attribute names or OIDs. 315 * @return This builder. 316 */ 317 public Builder requiredAttributes(final Collection<String> attributeNamesOrOIDs) { 318 this.requiredAttributes.addAll(attributeNamesOrOIDs); 319 return this; 320 } 321 322 /** 323 * Adds the provided required attributes. 324 * 325 * @param attributeNamesOrOIDs 326 * The list of required attribute names or OIDs. 327 * @return This builder. 328 */ 329 public Builder requiredAttributes(final String... attributeNamesOrOIDs) { 330 this.requiredAttributes.addAll(asList(attributeNamesOrOIDs)); 331 return this; 332 } 333 334 /** 335 * Adds the provided superior object classes. 336 * 337 * @param objectClassNamesOrOIDs 338 * The list of superior object classes names or OIDs. 339 * @return This builder. 340 */ 341 public Builder superiorObjectClasses(final Collection<String> objectClassNamesOrOIDs) { 342 this.superiorClasses.addAll(objectClassNamesOrOIDs); 343 return this; 344 } 345 346 /** 347 * Adds the provided superior object classes. 348 * 349 * @param objectClassNamesOrOIDs 350 * The list of superior object classes names or OIDs. 351 * @return This builder. 352 */ 353 public Builder superiorObjectClasses(final String... objectClassNamesOrOIDs) { 354 this.superiorClasses.addAll(asList(objectClassNamesOrOIDs)); 355 return this; 356 } 357 358 /** 359 * Sets the type of this object class. 360 * 361 * @param type 362 * The object class type. 363 * @return This builder. 364 */ 365 public Builder type(final ObjectClassType type) { 366 this.type = type; 367 return this; 368 } 369 } 370 371 /** The OID that may be used to reference this definition. */ 372 private final String oid; 373 374 /** The set of user defined names for this definition. */ 375 private final List<String> names; 376 377 /** Indicates whether this definition is declared "obsolete". */ 378 private final boolean isObsolete; 379 380 /** The reference to the superior objectclasses. */ 381 private final Set<String> superiorClassOIDs; 382 383 /** The objectclass type for this objectclass. */ 384 private final ObjectClassType objectClassType; 385 386 /** The set of required attribute types for this objectclass. */ 387 private final Set<String> requiredAttributeOIDs; 388 389 /** The set of optional attribute types for this objectclass. */ 390 private final Set<String> optionalAttributeOIDs; 391 392 private Set<ObjectClass> superiorClasses = emptySet(); 393 private Set<AttributeType> declaredRequiredAttributes = emptySet(); 394 private Set<AttributeType> requiredAttributes = emptySet(); 395 private Set<AttributeType> declaredOptionalAttributes = emptySet(); 396 private Set<AttributeType> optionalAttributes = emptySet(); 397 398 /** Indicates whether validation has been performed. */ 399 private boolean needsValidating = true; 400 401 /** Indicates whether validation failed. */ 402 private boolean isValid; 403 404 /** 405 * Indicates whether this object class is a placeholder. 406 * <p> 407 * A placeholder objectclass is returned by a non-strict schema when the requested 408 * object class does not exist in the schema. The placeholder is not registered to 409 * the schema. 410 * It is defined as an abstract object class, with no optional or required attribute. 411 * <p> 412 * A strict schema never returns a placeholder: it throws an UnknownSchemaElementException. 413 */ 414 private boolean isPlaceHolder; 415 416 /** Indicates whether this object class is the extensibleObject class. */ 417 private final boolean isExtensibleObject; 418 419 /** 420 * Construct a extensibleObject object class where the set of allowed 421 * attribute types of this object class is implicitly the set of all 422 * attribute types of userApplications usage. 423 * 424 * @param description 425 * The description for this schema definition 426 * @param extraProperties 427 * The map of "extra" properties for this schema definition 428 */ 429 static ObjectClass newExtensibleObjectObjectClass(final String description, 430 final Map<String, List<String>> extraProperties, final SchemaBuilder builder) { 431 return new ObjectClass(new Builder(EXTENSIBLE_OBJECT_OBJECTCLASS_OID, builder) 432 .description(description) 433 .extraProperties(extraProperties) 434 .names(EXTENSIBLE_OBJECT_OBJECTCLASS_NAME) 435 .superiorObjectClasses(TOP_OBJECTCLASS_NAME) 436 .type(AUXILIARY)); 437 } 438 439 /** 440 * Creates a new place-holder object class having the specified name. 441 * <p> 442 * A place-holder object class is never registered to a schema. 443 * <p> 444 * The OID of the place-holder object class will be the normalized object 445 * class name followed by the suffix "-oid". 446 * 447 * @param name 448 * The name of the place-holder object class. 449 */ 450 static ObjectClass newPlaceHolder(String name) { 451 return new ObjectClass(name); 452 } 453 454 private ObjectClass(final Builder builder) { 455 super(builder); 456 457 if (builder.oid == null || builder.oid.isEmpty()) { 458 throw new IllegalArgumentException("An OID must be specified."); 459 } 460 461 this.oid = builder.oid; 462 this.names = unmodifiableCopyOfList(builder.names); 463 this.isObsolete = builder.isObsolete; 464 this.superiorClassOIDs = unmodifiableCopyOfSet(builder.superiorClasses); 465 this.objectClassType = builder.type; 466 this.requiredAttributeOIDs = unmodifiableCopyOfSet(builder.requiredAttributes); 467 this.optionalAttributeOIDs = unmodifiableCopyOfSet(builder.optionalAttributes); 468 this.isExtensibleObject = oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID); 469 this.isPlaceHolder = false; 470 } 471 472 /** 473 * Creates a new place-holder object class having the specified name. 474 * The OID of the place-holder object class will be the normalized object class name 475 * followed by the suffix "-oid". 476 * 477 * @param name 478 * The name of the place-holder object class. 479 */ 480 private ObjectClass(final String name) { 481 this.oid = toOID(name); 482 this.names = Collections.singletonList(name); 483 this.isObsolete = false; 484 this.superiorClassOIDs = Collections.singleton(TOP_OBJECTCLASS_NAME); 485 this.objectClassType = ObjectClassType.ABSTRACT; 486 this.requiredAttributeOIDs = Collections.emptySet(); 487 this.optionalAttributeOIDs = Collections.emptySet(); 488 this.isExtensibleObject = oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID); 489 this.isPlaceHolder = true; 490 } 491 492 private static String toOID(final String name) { 493 final StringBuilder builder = new StringBuilder(name.length() + 4); 494 StaticUtils.toLowerCase(name, builder); 495 builder.append("-oid"); 496 return builder.toString(); 497 } 498 499 /** 500 * Returns {@code true} if the provided object is an object class having the 501 * same numeric OID as this object class. 502 * 503 * @param o 504 * The object to be compared. 505 * @return {@code true} if the provided object is a object class having the 506 * same numeric OID as this object class. 507 */ 508 @Override 509 public boolean equals(final Object o) { 510 if (this == o) { 511 return true; 512 } else if (o instanceof ObjectClass) { 513 final ObjectClass other = (ObjectClass) o; 514 return oid.equals(other.oid); 515 } else { 516 return false; 517 } 518 } 519 520 /** 521 * Returns an unmodifiable set containing the optional attributes for this 522 * object class. Note that this set will not automatically include any 523 * optional attributes for superior object classes. 524 * 525 * @return An unmodifiable set containing the optional attributes for this 526 * object class. 527 */ 528 public Set<AttributeType> getDeclaredOptionalAttributes() { 529 return declaredOptionalAttributes; 530 } 531 532 /** 533 * Returns an unmodifiable set containing the required attributes for this 534 * object class. Note that this set will not automatically include any 535 * required attributes for superior object classes. 536 * 537 * @return An unmodifiable set containing the required attributes for this 538 * object class. 539 */ 540 public Set<AttributeType> getDeclaredRequiredAttributes() { 541 return declaredRequiredAttributes; 542 } 543 544 /** 545 * Returns the name or OID for this schema definition. If it has one or more 546 * names, then the primary name will be returned. If it does not have any 547 * names, then the OID will be returned. 548 * 549 * @return The name or OID for this schema definition. 550 */ 551 public String getNameOrOID() { 552 if (names.isEmpty()) { 553 return oid; 554 } 555 return names.get(0); 556 } 557 558 /** 559 * Returns an unmodifiable list containing the user-defined names that may 560 * be used to reference this schema definition. 561 * 562 * @return Returns an unmodifiable list containing the user-defined names 563 * that may be used to reference this schema definition. 564 */ 565 public List<String> getNames() { 566 return names; 567 } 568 569 /** 570 * Returns the objectclass type for this objectclass. 571 * 572 * @return The objectclass type for this objectclass. 573 */ 574 public ObjectClassType getObjectClassType() { 575 return objectClassType != null ? objectClassType : STRUCTURAL; 576 } 577 578 /** 579 * Returns the OID for this schema definition. 580 * 581 * @return The OID for this schema definition. 582 */ 583 public String getOID() { 584 return oid; 585 } 586 587 /** 588 * Returns an unmodifiable set containing the optional attributes for this 589 * object class and any superior object classes that it might have. 590 * 591 * @return An unmodifiable set containing the optional attributes for this 592 * object class and any superior object classes that it might have. 593 */ 594 public Set<AttributeType> getOptionalAttributes() { 595 return optionalAttributes; 596 } 597 598 /** 599 * Returns an unmodifiable set containing the required attributes for this 600 * object class and any superior object classes that it might have. 601 * 602 * @return An unmodifiable set containing the required attributes for this 603 * object class and any superior object classes that it might have. 604 */ 605 public Set<AttributeType> getRequiredAttributes() { 606 return requiredAttributes; 607 } 608 609 /** 610 * Returns an unmodifiable set containing the superior classes for this 611 * object class. 612 * 613 * @return An unmodifiable set containing the superior classes for this 614 * object class. 615 */ 616 public Set<ObjectClass> getSuperiorClasses() { 617 return superiorClasses; 618 } 619 620 /** 621 * Returns the hash code for this object class. It will be calculated as the 622 * hash code of the numeric OID. 623 * 624 * @return The hash code for this object class. 625 */ 626 @Override 627 public int hashCode() { 628 return oid.hashCode(); 629 } 630 631 /** 632 * Indicates whether this schema definition has the specified name. 633 * 634 * @param name 635 * The name for which to make the determination. 636 * @return <code>true</code> if the specified name is assigned to this 637 * schema definition, or <code>false</code> if not. 638 */ 639 public boolean hasName(final String name) { 640 for (final String n : names) { 641 if (n.equalsIgnoreCase(name)) { 642 return true; 643 } 644 } 645 return false; 646 } 647 648 /** 649 * Indicates whether this schema definition has the specified name or OID. 650 * 651 * @param value 652 * The value for which to make the determination. 653 * @return <code>true</code> if the provided value matches the OID or one of 654 * the names assigned to this schema definition, or 655 * <code>false</code> if not. 656 */ 657 public boolean hasNameOrOID(final String value) { 658 return hasName(value) || getOID().equals(value); 659 } 660 661 /** 662 * Indicates whether this objectclass is a descendant of the provided class. 663 * 664 * @param objectClass 665 * The objectClass for which to make the determination. 666 * @return <code>true</code> if this objectclass is a descendant of the 667 * provided class, or <code>false</code> if not. 668 */ 669 public boolean isDescendantOf(final ObjectClass objectClass) { 670 for (final ObjectClass sup : superiorClasses) { 671 if (sup.equals(objectClass) || sup.isDescendantOf(objectClass)) { 672 return true; 673 } 674 } 675 return false; 676 } 677 678 /** 679 * Indicates whether this object class is extensibleObject class. 680 * <p> 681 * An extensible object class has an optional attributes list corresponding 682 * to all the attributes types defined in the schema. It means any attribute 683 * type can be used with this object class. 684 * 685 * @return {@code true} if this object class is extensible. 686 */ 687 public boolean isExtensible() { 688 return isExtensibleObject; 689 } 690 691 692 /** 693 * Indicates whether this schema definition is declared "obsolete". 694 * 695 * @return <code>true</code> if this schema definition is declared 696 * "obsolete", or <code>false</code> if not. 697 */ 698 public boolean isObsolete() { 699 return isObsolete; 700 } 701 702 /** 703 * Returns whether this object class is a placeholder, 704 * i.e. a dummy object class that does not exist in the schema. 705 * 706 * @return {@code true} if this object class is a placeholder, 707 * {@code false} otherwise 708 * @deprecated This method may be removed at any time 709 * @since OPENDJ-2987 Migrate ObjectClass 710 */ 711 @Deprecated 712 public boolean isPlaceHolder() { 713 return isPlaceHolder; 714 } 715 716 /** 717 * Indicates whether the provided attribute type is included in the optional 718 * attribute list for this or any of its superior objectclasses. 719 * 720 * @param attributeType 721 * The attribute type for which to make the determination. 722 * @return <code>true</code> if the provided attribute type is optional for 723 * this objectclass or any of its superior classes, or 724 * <code>false</code> if not. 725 */ 726 public boolean isOptional(final AttributeType attributeType) { 727 // In theory, attribute types not defined in the schema (i.e place holder attributes) should 728 // not be considered as optional. 729 // However, in practice, some parts of the server have historically relied on non-defined 730 // attributes to behave properly. 731 return isExtensibleObject || optionalAttributes.contains(attributeType); 732 } 733 734 /** 735 * Indicates whether the provided attribute type is included in the required 736 * attribute list for this or any of its superior objectclasses. 737 * 738 * @param attributeType 739 * The attribute type for which to make the determination. 740 * @return <code>true</code> if the provided attribute type is required by 741 * this objectclass or any of its superior classes, or 742 * <code>false</code> if not. 743 */ 744 public boolean isRequired(final AttributeType attributeType) { 745 return requiredAttributes.contains(attributeType); 746 } 747 748 /** 749 * Indicates whether the provided attribute type is in the list of required 750 * or optional attributes for this objectclass or any of its superior 751 * classes. 752 * 753 * @param attributeType 754 * The attribute type for which to make the determination. 755 * @return <code>true</code> if the provided attribute type is required or 756 * allowed for this objectclass or any of its superior classes, or 757 * <code>false</code> if it is not. 758 */ 759 public boolean isRequiredOrOptional(final AttributeType attributeType) { 760 return isRequired(attributeType) || isOptional(attributeType); 761 } 762 763 @Override 764 void toStringContent(final StringBuilder buffer) { 765 buffer.append(oid); 766 767 if (!names.isEmpty()) { 768 final Iterator<String> iterator = names.iterator(); 769 770 final String firstName = iterator.next(); 771 if (iterator.hasNext()) { 772 buffer.append(" NAME ( '"); 773 buffer.append(firstName); 774 775 while (iterator.hasNext()) { 776 buffer.append("' '"); 777 buffer.append(iterator.next()); 778 } 779 780 buffer.append("' )"); 781 } else { 782 buffer.append(" NAME '"); 783 buffer.append(firstName); 784 buffer.append("'"); 785 } 786 } 787 788 appendDescription(buffer); 789 790 if (isObsolete) { 791 buffer.append(" OBSOLETE"); 792 } 793 794 if (!superiorClassOIDs.isEmpty()) { 795 final Iterator<String> iterator = superiorClassOIDs.iterator(); 796 797 final String firstName = iterator.next(); 798 if (iterator.hasNext()) { 799 buffer.append(" SUP ( "); 800 buffer.append(firstName); 801 802 while (iterator.hasNext()) { 803 buffer.append(" $ "); 804 buffer.append(iterator.next()); 805 } 806 807 buffer.append(" )"); 808 } else { 809 buffer.append(" SUP "); 810 buffer.append(firstName); 811 } 812 } 813 814 if (objectClassType != null) { 815 buffer.append(" "); 816 buffer.append(objectClassType); 817 } 818 819 if (!requiredAttributeOIDs.isEmpty()) { 820 final Iterator<String> iterator = requiredAttributeOIDs.iterator(); 821 822 final String firstName = iterator.next(); 823 if (iterator.hasNext()) { 824 buffer.append(" MUST ( "); 825 buffer.append(firstName); 826 827 while (iterator.hasNext()) { 828 buffer.append(" $ "); 829 buffer.append(iterator.next()); 830 } 831 832 buffer.append(" )"); 833 } else { 834 buffer.append(" MUST "); 835 buffer.append(firstName); 836 } 837 } 838 839 if (!isExtensible() && !optionalAttributeOIDs.isEmpty()) { 840 final Iterator<String> iterator = optionalAttributeOIDs.iterator(); 841 842 final String firstName = iterator.next(); 843 if (iterator.hasNext()) { 844 buffer.append(" MAY ( "); 845 buffer.append(firstName); 846 847 while (iterator.hasNext()) { 848 buffer.append(" $ "); 849 buffer.append(iterator.next()); 850 } 851 852 buffer.append(" )"); 853 } else { 854 buffer.append(" MAY "); 855 buffer.append(firstName); 856 } 857 } 858 } 859 860 boolean validate(final Schema schema, final List<ObjectClass> invalidSchemaElements, 861 final List<LocalizableMessage> warnings) { 862 // Avoid validating this schema element more than once. 863 // This may occur if multiple object classes specify the same superior. 864 if (!needsValidating) { 865 return isValid; 866 } 867 868 // Prevent re-validation. 869 needsValidating = false; 870 871 // Init a flag to check to inheritance from top (only needed for 872 // structural object classes) per RFC 4512 873 boolean derivesTop = getObjectClassType() != ObjectClassType.STRUCTURAL; 874 875 if (!superiorClassOIDs.isEmpty()) { 876 superiorClasses = new HashSet<>(superiorClassOIDs.size()); 877 ObjectClass superiorClass; 878 for (final String superClassOid : superiorClassOIDs) { 879 try { 880 superiorClass = schema.getObjectClass(superClassOid); 881 } catch (final UnknownSchemaElementException e) { 882 final LocalizableMessage message = 883 WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_SUPERIOR_CLASS1.get( 884 getNameOrOID(), superClassOid); 885 failValidation(invalidSchemaElements, warnings, message); 886 return false; 887 } 888 889 // Make sure that the inheritance configuration is acceptable. 890 final ObjectClassType superiorType = superiorClass.getObjectClassType(); 891 final ObjectClassType type = getObjectClassType(); 892 switch (type) { 893 case ABSTRACT: 894 // Abstract classes may only inherit from other abstract classes. 895 if (superiorType != ObjectClassType.ABSTRACT) { 896 final LocalizableMessage message = 897 WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE1.get( 898 getNameOrOID(), type.toString(), superiorType 899 .toString(), superiorClass.getNameOrOID()); 900 failValidation(invalidSchemaElements, warnings, message); 901 return false; 902 } 903 break; 904 905 case AUXILIARY: 906 // Auxiliary classes may only inherit from abstract classes 907 // or other auxiliary classes. 908 if (superiorType != ObjectClassType.ABSTRACT 909 && superiorType != ObjectClassType.AUXILIARY) { 910 final LocalizableMessage message = 911 WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE1.get( 912 getNameOrOID(), type.toString(), superiorType 913 .toString(), superiorClass.getNameOrOID()); 914 failValidation(invalidSchemaElements, warnings, message); 915 return false; 916 } 917 break; 918 919 case STRUCTURAL: 920 // Structural classes may only inherit from abstract classes 921 // or other structural classes. 922 if (superiorType != ObjectClassType.ABSTRACT 923 && superiorType != ObjectClassType.STRUCTURAL) { 924 final LocalizableMessage message = 925 WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE1.get( 926 getNameOrOID(), type.toString(), superiorType 927 .toString(), superiorClass.getNameOrOID()); 928 failValidation(invalidSchemaElements, warnings, message); 929 return false; 930 } 931 break; 932 } 933 934 // All existing structural object classes defined in this schema 935 // are implicitly guaranteed to inherit from top. 936 if (!derivesTop && superiorType == ObjectClassType.STRUCTURAL) { 937 derivesTop = true; 938 } 939 940 // First ensure that the superior has been validated and fail if 941 // it is invalid. 942 if (!superiorClass.validate(schema, invalidSchemaElements, warnings)) { 943 final LocalizableMessage message = 944 WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_CLASS.get(getNameOrOID(), 945 superClassOid); 946 failValidation(invalidSchemaElements, warnings, message); 947 return false; 948 } 949 950 // Inherit all required attributes from superior class. 951 final Set<AttributeType> supRequiredAttrs = superiorClass.getRequiredAttributes(); 952 if (!supRequiredAttrs.isEmpty()) { 953 if (requiredAttributes == Collections.EMPTY_SET) { 954 requiredAttributes = new HashSet<>(supRequiredAttrs); 955 } else { 956 requiredAttributes.addAll(supRequiredAttrs); 957 } 958 } 959 960 // Inherit all optional attributes from superior class. 961 final Set<AttributeType> supOptionalAttrs = superiorClass.getOptionalAttributes(); 962 if (!supOptionalAttrs.isEmpty()) { 963 if (optionalAttributes == Collections.EMPTY_SET) { 964 optionalAttributes = new HashSet<>(supOptionalAttrs); 965 } else { 966 optionalAttributes.addAll(supOptionalAttrs); 967 } 968 } 969 970 superiorClasses.add(superiorClass); 971 } 972 } else if (superiorClasses.isEmpty() && getObjectClassType() == ObjectClassType.STRUCTURAL) { 973 // default superior to top 974 superiorClasses = new HashSet<>(1); 975 superiorClasses.add(schema.getObjectClass(TOP_OBJECTCLASS_OID)); 976 derivesTop = true; 977 } 978 979 if (!derivesTop) { 980 derivesTop = isDescendantOf(schema.getObjectClass(TOP_OBJECTCLASS_OID)); 981 } 982 983 // Structural classes must have the "top" objectclass somewhere 984 // in the superior chain. 985 if (!derivesTop) { 986 final LocalizableMessage message = 987 WARN_ATTR_SYNTAX_OBJECTCLASS_STRUCTURAL_SUPERIOR_NOT_TOP1.get(getNameOrOID()); 988 failValidation(invalidSchemaElements, warnings, message); 989 return false; 990 } 991 992 if (isExtensible()) { 993 Collection<AttributeType> attributeTypes = schema.getAttributeTypes(); 994 declaredOptionalAttributes = new HashSet<>(attributeTypes.size()); 995 for (final AttributeType attributeType : attributeTypes) { 996 if (attributeType.getUsage() == AttributeUsage.USER_APPLICATIONS 997 && !requiredAttributes.contains(attributeType)) { 998 declaredOptionalAttributes.add(attributeType); 999 } 1000 } 1001 optionalAttributes = declaredOptionalAttributes; 1002 } else { 1003 if (!requiredAttributeOIDs.isEmpty()) { 1004 declaredRequiredAttributes = new HashSet<>(requiredAttributeOIDs.size()); 1005 AttributeType attributeType; 1006 for (final String requiredAttribute : requiredAttributeOIDs) { 1007 try { 1008 attributeType = schema.getAttributeType(requiredAttribute); 1009 } catch (final UnknownSchemaElementException e) { 1010 // This isn't good because it means that the objectclass 1011 // requires an attribute type that we don't know anything about. 1012 final LocalizableMessage message = 1013 WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_REQUIRED_ATTR1.get( 1014 getNameOrOID(), requiredAttribute); 1015 failValidation(invalidSchemaElements, warnings, message); 1016 return false; 1017 } 1018 declaredRequiredAttributes.add(attributeType); 1019 } 1020 if (requiredAttributes == Collections.EMPTY_SET) { 1021 requiredAttributes = declaredRequiredAttributes; 1022 } else { 1023 requiredAttributes.addAll(declaredRequiredAttributes); 1024 } 1025 } 1026 1027 if (!optionalAttributeOIDs.isEmpty()) { 1028 declaredOptionalAttributes = new HashSet<>(optionalAttributeOIDs.size()); 1029 AttributeType attributeType; 1030 for (final String optionalAttribute : optionalAttributeOIDs) { 1031 try { 1032 attributeType = schema.getAttributeType(optionalAttribute); 1033 } catch (final UnknownSchemaElementException e) { 1034 // This isn't good because it means that the objectclass 1035 // requires an attribute type that we don't know anything about. 1036 final LocalizableMessage message = 1037 WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_OPTIONAL_ATTR1.get( 1038 getNameOrOID(), optionalAttribute); 1039 failValidation(invalidSchemaElements, warnings, message); 1040 return false; 1041 } 1042 declaredOptionalAttributes.add(attributeType); 1043 } 1044 if (optionalAttributes == Collections.EMPTY_SET) { 1045 optionalAttributes = declaredOptionalAttributes; 1046 } else { 1047 optionalAttributes.addAll(declaredOptionalAttributes); 1048 } 1049 } 1050 } 1051 1052 declaredOptionalAttributes = Collections.unmodifiableSet(declaredOptionalAttributes); 1053 declaredRequiredAttributes = Collections.unmodifiableSet(declaredRequiredAttributes); 1054 optionalAttributes = Collections.unmodifiableSet(optionalAttributes); 1055 requiredAttributes = Collections.unmodifiableSet(requiredAttributes); 1056 superiorClasses = Collections.unmodifiableSet(superiorClasses); 1057 1058 return isValid = true; 1059 } 1060 1061 private void failValidation(final List<ObjectClass> invalidSchemaElements, 1062 final List<LocalizableMessage> warnings, final LocalizableMessage message) { 1063 invalidSchemaElements.add(this); 1064 warnings.add(ERR_OC_VALIDATION_FAIL.get(toString(), message)); 1065 } 1066}