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 java.util.Arrays.*;
020
021import static org.forgerock.opendj.ldap.schema.SchemaUtils.*;
022
023import static com.forgerock.opendj.ldap.CoreMessages.*;
024
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.Iterator;
029import java.util.LinkedHashSet;
030import java.util.LinkedList;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034
035import org.forgerock.i18n.LocalizableMessage;
036import org.forgerock.util.Reject;
037
038/**
039 * This class defines a DIT structure rule, which is used to indicate the types
040 * of children that entries may have.
041 */
042public final class DITStructureRule extends AbstractSchemaElement {
043
044    /** A fluent API for incrementally constructing DIT structure rules. */
045    public static final class Builder extends SchemaElementBuilder<Builder> {
046        private int ruleID;
047        private final List<String> names = new LinkedList<>();
048        private boolean isObsolete;
049        private String nameFormOID;
050        private final Set<Integer> superiorRuleIDs = new LinkedHashSet<>();
051
052        Builder(final DITStructureRule structureRule, final SchemaBuilder builder) {
053            super(builder, structureRule);
054            this.ruleID = structureRule.ruleID;
055            this.names.addAll(structureRule.names);
056            this.isObsolete = structureRule.isObsolete;
057            this.nameFormOID = structureRule.nameFormOID;
058            this.superiorRuleIDs.addAll(structureRule.superiorRuleIDs);
059        }
060
061        Builder(final Integer ruleID, final SchemaBuilder schemaBuilder) {
062            super(schemaBuilder);
063            this.ruleID = ruleID;
064        }
065
066        /**
067         * Adds this DIT structure rule to the schema, throwing a
068         * {@code  ConflictingSchemaElementException} if there is an existing DIT
069         * structure rule with the same numeric ID.
070         *
071         * @return The parent schema builder.
072         * @throws ConflictingSchemaElementException
073         *             If there is an existing structure rule with the same
074         *             numeric ID.
075         */
076        public SchemaBuilder addToSchema() {
077            return getSchemaBuilder().addDITStructureRule(new DITStructureRule(this), false);
078        }
079
080        /**
081         * Adds this DIT structure rule to the schema overwriting any existing
082         * DIT structure rule with the same numeric ID.
083         *
084         * @return The parent schema builder.
085         */
086        public SchemaBuilder addToSchemaOverwrite() {
087            return getSchemaBuilder().addDITStructureRule(new DITStructureRule(this), true);
088        }
089
090        /**
091         * Adds this DIT structure rule to the schema, overwriting any existing DIT structure rule
092         * with the same numeric OID if the overwrite parameter is set to {@code true}.
093         *
094         * @param overwrite
095         *            {@code true} if any DIT structure rule with the same OID should be overwritten.
096         * @return The parent schema builder.
097         */
098        SchemaBuilder addToSchema(final boolean overwrite) {
099            return overwrite ? addToSchemaOverwrite() : addToSchema();
100        }
101
102        @Override
103        public Builder description(final String description) {
104            return description0(description);
105        }
106
107        @Override
108        public Builder extraProperties(final Map<String, List<String>> extraProperties) {
109            return extraProperties0(extraProperties);
110        }
111
112        @Override
113        public Builder extraProperties(final String extensionName, final String... extensionValues) {
114            return extraProperties0(extensionName, extensionValues);
115        }
116
117        @Override
118        Builder getThis() {
119            return this;
120        }
121
122        /**
123         * Sets the name form associated with the DIT structure rule.
124         *
125         * @param nameFormOID
126         *            The name form numeric OID.
127         * @return This builder.
128         */
129        public Builder nameForm(final String nameFormOID) {
130            this.nameFormOID = nameFormOID;
131            return this;
132        }
133
134        /**
135         * Adds the provided user friendly names.
136         *
137         * @param names
138         *            The user friendly names.
139         * @return This builder.
140         */
141        public Builder names(final Collection<String> names) {
142            this.names.addAll(names);
143            return this;
144        }
145
146        /**
147         * Adds the provided user friendly names.
148         *
149         * @param names
150         *            The user friendly names.
151         * @return This builder.
152         */
153        public Builder names(final String... names) {
154            return names(asList(names));
155        }
156
157        /**
158         * Specifies whether this schema element is obsolete.
159         *
160         * @param isObsolete
161         *            {@code true} if this schema element is obsolete
162         *            (default is {@code false}).
163         * @return This builder.
164         */
165        public Builder obsolete(final boolean isObsolete) {
166            this.isObsolete = isObsolete;
167            return this;
168        }
169
170        @Override
171        public Builder removeAllExtraProperties() {
172            return removeAllExtraProperties0();
173        }
174
175        /**
176         * Removes all user defined names.
177         *
178         * @return This builder.
179         */
180        public Builder removeAllNames() {
181            this.names.clear();
182            return this;
183        }
184
185        /**
186         * Removes all superior rules.
187         *
188         * @return This builder.
189         */
190        public Builder removeAllSuperiorRules() {
191            this.superiorRuleIDs.clear();
192            return this;
193        }
194
195        @Override
196        public Builder removeExtraProperty(final String extensionName, final String... extensionValues) {
197            return removeExtraProperty0(extensionName, extensionValues);
198        }
199
200        /**
201         * Removes the provided user defined name.
202         *
203         * @param name
204         *            The user defined name to be removed.
205         * @return This builder.
206         */
207        public Builder removeName(final String name) {
208            this.names.remove(name);
209            return this;
210        }
211
212        /**
213         * Removes the provided superior rule.
214         *
215         * @param superiorRuleID
216         *            The superior rule ID to be removed.
217         * @return This builder.
218         */
219        public Builder removeSuperiorRule(final int superiorRuleID) {
220            this.superiorRuleIDs.remove(superiorRuleID);
221            return this;
222        }
223
224        /**
225         * Sets the the numeric ID which uniquely identifies this structure rule.
226         *
227         * @param ruleID
228         *            The numeric ID.
229         * @return This builder.
230         */
231        public Builder ruleID(final int ruleID) {
232            this.ruleID = ruleID;
233            return this;
234        }
235
236        /**
237         * Adds the provided superior rule identifiers.
238         *
239         * @param superiorRuleIDs
240         *            Structure rule identifiers.
241         * @return This builder.
242         */
243        public Builder superiorRules(final int... superiorRuleIDs) {
244            for (int ruleID : superiorRuleIDs) {
245                this.superiorRuleIDs.add(ruleID);
246            }
247            return this;
248        }
249
250        Builder superiorRules(final Collection<Integer> superiorRuleIDs) {
251            this.superiorRuleIDs.addAll(superiorRuleIDs);
252            return this;
253        }
254    }
255
256    /** The rule ID for this DIT structure rule. */
257    private final Integer ruleID;
258
259    /** The set of user defined names for this definition. */
260    private final List<String> names;
261
262    /** Indicates whether this definition is declared "obsolete". */
263    private final boolean isObsolete;
264
265    /** The name form for this DIT structure rule. */
266    private final String nameFormOID;
267
268    /** The set of superior DIT structure rules. */
269    private final Set<Integer> superiorRuleIDs;
270
271    private NameForm nameForm;
272    private Set<DITStructureRule> superiorRules = Collections.emptySet();
273
274    /** Indicates whether validation has been performed. */
275    private boolean needsValidating = true;
276
277    /** The indicates whether validation failed. */
278    private boolean isValid;
279
280    DITStructureRule(final Builder builder) {
281        super(builder);
282        Reject.ifNull(builder.nameFormOID);
283
284        this.ruleID = builder.ruleID;
285        this.names = unmodifiableCopyOfList(builder.names);
286        this.isObsolete = builder.isObsolete;
287        this.nameFormOID = builder.nameFormOID;
288        this.superiorRuleIDs = unmodifiableCopyOfSet(builder.superiorRuleIDs);
289    }
290
291    /**
292     * Returns {@code true} if the provided object is a DIT structure rule
293     * having the same rule ID as this DIT structure rule.
294     *
295     * @param o
296     *            The object to be compared.
297     * @return {@code true} if the provided object is a DIT structure rule
298     *         having the same rule ID as this DIT structure rule.
299     */
300    @Override
301    public boolean equals(final Object o) {
302        if (this == o) {
303            return true;
304        } else if (o instanceof DITStructureRule) {
305            final DITStructureRule other = (DITStructureRule) o;
306            return ruleID.equals(other.ruleID);
307        } else {
308            return false;
309        }
310    }
311
312    /**
313     * Retrieves the name form for this DIT structure rule.
314     *
315     * @return The name form for this DIT structure rule.
316     */
317    public NameForm getNameForm() {
318        return nameForm;
319    }
320
321    /**
322     * Retrieves the name or rule ID for this schema definition. If it has one
323     * or more names, then the primary name will be returned. If it does not
324     * have any names, then the OID will be returned.
325     *
326     * @return The name or OID for this schema definition.
327     */
328    public String getNameOrRuleID() {
329        if (names.isEmpty()) {
330            return ruleID.toString();
331        }
332        return names.get(0);
333    }
334
335    /**
336     * Returns an unmodifiable list containing the user-defined names that may
337     * be used to reference this schema definition.
338     *
339     * @return Returns an unmodifiable list containing the user-defined names
340     *         that may be used to reference this schema definition.
341     */
342    public List<String> getNames() {
343        return names;
344    }
345
346    /**
347     * Retrieves the rule ID for this DIT structure rule.
348     *
349     * @return The rule ID for this DIT structure rule (never {@code null}).
350     */
351    public Integer getRuleID() {
352        return ruleID;
353    }
354
355    /**
356     * Returns an unmodifiable set containing the superior rules for this DIT
357     * structure rule.
358     *
359     * @return An unmodifiable set containing the superior rules for this DIT
360     *         structure rule.
361     */
362    public Set<DITStructureRule> getSuperiorRules() {
363        return superiorRules;
364    }
365
366    /**
367     * Returns the hash code for this DIT structure rule. It will be calculated
368     * as the hash code of the rule ID.
369     *
370     * @return The hash code for this DIT structure rule.
371     */
372    @Override
373    public int hashCode() {
374        return ruleID.hashCode();
375    }
376
377    /**
378     * Indicates whether this schema definition has the specified name.
379     *
380     * @param name
381     *            The name for which to make the determination.
382     * @return <code>true</code> if the specified name is assigned to this
383     *         schema definition, or <code>false</code> if not.
384     */
385    public boolean hasName(final String name) {
386        for (final String n : names) {
387            if (n.equalsIgnoreCase(name)) {
388                return true;
389            }
390        }
391        return false;
392    }
393
394    /**
395     * Indicates whether this schema definition is declared "obsolete".
396     *
397     * @return <code>true</code> if this schema definition is declared
398     *         "obsolete", or <code>false</code> if not.
399     */
400    public boolean isObsolete() {
401        return isObsolete;
402    }
403
404    @Override
405    void toStringContent(final StringBuilder buffer) {
406        buffer.append(ruleID);
407
408        if (!names.isEmpty()) {
409            final Iterator<String> iterator = names.iterator();
410
411            final String firstName = iterator.next();
412            if (iterator.hasNext()) {
413                buffer.append(" NAME ( '");
414                buffer.append(firstName);
415
416                while (iterator.hasNext()) {
417                    buffer.append("' '");
418                    buffer.append(iterator.next());
419                }
420
421                buffer.append("' )");
422            } else {
423                buffer.append(" NAME '");
424                buffer.append(firstName);
425                buffer.append("'");
426            }
427        }
428
429        appendDescription(buffer);
430
431        if (isObsolete) {
432            buffer.append(" OBSOLETE");
433        }
434
435        buffer.append(" FORM ");
436        buffer.append(nameFormOID);
437
438        if (superiorRuleIDs != null && !superiorRuleIDs.isEmpty()) {
439            final Iterator<Integer> iterator = superiorRuleIDs.iterator();
440
441            final Integer firstRule = iterator.next();
442            if (iterator.hasNext()) {
443                buffer.append(" SUP ( ");
444                buffer.append(firstRule);
445
446                while (iterator.hasNext()) {
447                    buffer.append(" ");
448                    buffer.append(iterator.next());
449                }
450
451                buffer.append(" )");
452            } else {
453                buffer.append(" SUP ");
454                buffer.append(firstRule);
455            }
456        }
457    }
458
459    boolean validate(final Schema schema, final List<DITStructureRule> invalidSchemaElements,
460            final List<LocalizableMessage> warnings) {
461        // Avoid validating this schema element more than once.
462        // This may occur if multiple rules specify the same superior.
463        if (!needsValidating) {
464            return isValid;
465        }
466
467        // Prevent re-validation.
468        needsValidating = false;
469
470        try {
471            nameForm = schema.getNameForm(nameFormOID);
472        } catch (final UnknownSchemaElementException e) {
473            final LocalizableMessage message =
474                    ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM.get(getNameOrRuleID(), nameFormOID);
475            failValidation(invalidSchemaElements, warnings, message);
476            return false;
477        }
478
479        if (!superiorRuleIDs.isEmpty()) {
480            superiorRules = new HashSet<>(superiorRuleIDs.size());
481            for (final Integer id : superiorRuleIDs) {
482                try {
483                    superiorRules.add(schema.getDITStructureRule(id));
484                } catch (final UnknownSchemaElementException e) {
485                    final LocalizableMessage message =
486                            ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(getNameOrRuleID(), id);
487                    failValidation(invalidSchemaElements, warnings, message);
488                    return false;
489                }
490            }
491        }
492        superiorRules = Collections.unmodifiableSet(superiorRules);
493
494        return isValid = true;
495    }
496
497    private void failValidation(final List<DITStructureRule> invalidSchemaElements,
498            final List<LocalizableMessage> warnings, final LocalizableMessage message) {
499        invalidSchemaElements.add(this);
500        warnings.add(ERR_DSR_VALIDATION_FAIL.get(toString(), message));
501    }
502}