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