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}