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