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-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 * Portions Copyright 2014 Manuel Gaupp
017 */
018package org.forgerock.opendj.ldap.schema;
019
020import static com.forgerock.opendj.ldap.CoreMessages.*;
021
022import static org.forgerock.opendj.ldap.AttributeDescription.*;
023
024import java.util.Collection;
025import java.util.Collections;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Map;
029
030import org.forgerock.i18n.LocalizableMessage;
031import org.forgerock.opendj.ldap.AVA;
032import org.forgerock.opendj.ldap.Attribute;
033import org.forgerock.opendj.ldap.AttributeDescription;
034import org.forgerock.opendj.ldap.Attributes;
035import org.forgerock.opendj.ldap.ByteString;
036import org.forgerock.opendj.ldap.Connection;
037import org.forgerock.opendj.ldap.DN;
038import org.forgerock.opendj.ldap.Entries;
039import org.forgerock.opendj.ldap.Entry;
040import org.forgerock.opendj.ldap.EntryNotFoundException;
041import org.forgerock.opendj.ldap.LdapException;
042import org.forgerock.opendj.ldap.LdapPromise;
043import org.forgerock.opendj.ldap.LinkedAttribute;
044import org.forgerock.opendj.ldap.RDN;
045import org.forgerock.util.Function;
046import org.forgerock.util.Option;
047import org.forgerock.util.Options;
048import org.forgerock.util.Reject;
049
050import com.forgerock.opendj.util.StaticUtils;
051
052/**
053 * This class defines a data structure that holds information about the
054 * components of the LDAP schema. It includes the following kinds of elements:
055 * <UL>
056 * <LI>Attribute type definitions</LI>
057 * <LI>Object class definitions</LI>
058 * <LI>Attribute syntax definitions</LI>
059 * <LI>Matching rule definitions</LI>
060 * <LI>Matching rule use definitions</LI>
061 * <LI>DIT content rule definitions</LI>
062 * <LI>DIT structure rule definitions</LI>
063 * <LI>Name form definitions</LI>
064 * </UL>
065 */
066public final class Schema {
067    private static interface Impl {
068        Schema asNonStrictSchema();
069
070        Schema asStrictSchema();
071
072        Options getOptions();
073
074        MatchingRule getDefaultMatchingRule();
075
076        Syntax getDefaultSyntax();
077
078        AttributeType getAttributeType(Schema schema, String nameOrOid);
079
080        AttributeType getAttributeType(String nameOrOid, Syntax syntax);
081
082        Collection<AttributeType> getAttributeTypes();
083
084        List<AttributeType> getAttributeTypesWithName(String name);
085
086        DITContentRule getDITContentRule(ObjectClass structuralClass);
087
088        DITContentRule getDITContentRule(String nameOrOid);
089
090        Collection<DITContentRule> getDITContentRules();
091
092        Collection<DITContentRule> getDITContentRulesWithName(String name);
093
094        DITStructureRule getDITStructureRule(int ruleID);
095
096        Collection<DITStructureRule> getDITStructureRules(NameForm nameForm);
097
098        Collection<DITStructureRule> getDITStructureRulesWithName(String name);
099
100        Collection<DITStructureRule> getDITStuctureRules();
101
102        MatchingRule getMatchingRule(String nameOrOid);
103
104        Collection<MatchingRule> getMatchingRules();
105
106        Collection<MatchingRule> getMatchingRulesWithName(String name);
107
108        MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule);
109
110        MatchingRuleUse getMatchingRuleUse(String nameOrOid);
111
112        Collection<MatchingRuleUse> getMatchingRuleUses();
113
114        Collection<MatchingRuleUse> getMatchingRuleUsesWithName(String name);
115
116        NameForm getNameForm(String nameOrOid);
117
118        Collection<NameForm> getNameForms();
119
120        Collection<NameForm> getNameForms(ObjectClass structuralClass);
121
122        Collection<NameForm> getNameFormsWithName(String name);
123
124        ObjectClass getObjectClass(String nameOrOid);
125
126        Collection<ObjectClass> getObjectClasses();
127
128        Collection<ObjectClass> getObjectClassesWithName(String name);
129
130        String getSchemaName();
131
132        Syntax getSyntax(Schema schema, String numericOID);
133
134        Collection<Syntax> getSyntaxes();
135
136        Collection<LocalizableMessage> getWarnings();
137
138        boolean hasAttributeType(String nameOrOid);
139
140        boolean hasDITContentRule(String nameOrOid);
141
142        boolean hasDITStructureRule(int ruleID);
143
144        boolean hasMatchingRule(String nameOrOid);
145
146        boolean hasMatchingRuleUse(String nameOrOid);
147
148        boolean hasNameForm(String nameOrOid);
149
150        boolean hasObjectClass(String nameOrOid);
151
152        boolean hasSyntax(String numericOID);
153
154        boolean isStrict();
155    }
156
157    private static final class NonStrictImpl implements Impl {
158        private final StrictImpl strictImpl;
159
160        private NonStrictImpl(final StrictImpl strictImpl) {
161            this.strictImpl = strictImpl;
162        }
163
164        @Override
165        public Schema asNonStrictSchema() {
166            return strictImpl.asNonStrictSchema();
167        }
168
169        @Override
170        public Schema asStrictSchema() {
171            return strictImpl.asStrictSchema();
172        }
173
174        @Override
175        public Options getOptions() {
176            return strictImpl.getOptions();
177        }
178
179        @Override
180        public Syntax getDefaultSyntax() {
181            return strictImpl.getDefaultSyntax();
182        }
183
184        @Override
185        public MatchingRule getDefaultMatchingRule() {
186            return strictImpl.getDefaultMatchingRule();
187        }
188
189        @Override
190        public AttributeType getAttributeType(final Schema schema, final String nameOrOid) {
191            return getAttributeType0(nameOrOid, schema.getDefaultSyntax(), schema.getDefaultMatchingRule());
192        }
193
194        @Override
195        public AttributeType getAttributeType(final String nameOrOid, final Syntax syntax) {
196            return getAttributeType0(nameOrOid, syntax, syntax.getEqualityMatchingRule());
197        }
198
199        private AttributeType getAttributeType0(String nameOrOid, Syntax syntax, MatchingRule equalityMatchingRule) {
200            final AttributeType type = strictImpl.getAttributeType0(nameOrOid);
201            return type != null ? type : AttributeType.newPlaceHolder(nameOrOid, syntax, equalityMatchingRule);
202        }
203
204        @Override
205        public Collection<AttributeType> getAttributeTypes() {
206            return strictImpl.getAttributeTypes();
207        }
208
209        @Override
210        public List<AttributeType> getAttributeTypesWithName(final String name) {
211            return strictImpl.getAttributeTypesWithName(name);
212        }
213
214        @Override
215        public DITContentRule getDITContentRule(final ObjectClass structuralClass) {
216            return strictImpl.getDITContentRule(structuralClass);
217        }
218
219        @Override
220        public DITContentRule getDITContentRule(final String nameOrOid) {
221            return strictImpl.getDITContentRule(nameOrOid);
222        }
223
224        @Override
225        public Collection<DITContentRule> getDITContentRules() {
226            return strictImpl.getDITContentRules();
227        }
228
229        @Override
230        public Collection<DITContentRule> getDITContentRulesWithName(final String name) {
231            return strictImpl.getDITContentRulesWithName(name);
232        }
233
234        @Override
235        public DITStructureRule getDITStructureRule(final int ruleID) {
236            return strictImpl.getDITStructureRule(ruleID);
237        }
238
239        @Override
240        public Collection<DITStructureRule> getDITStructureRules(final NameForm nameForm) {
241            return strictImpl.getDITStructureRules(nameForm);
242        }
243
244        @Override
245        public Collection<DITStructureRule> getDITStructureRulesWithName(final String name) {
246            return strictImpl.getDITStructureRulesWithName(name);
247        }
248
249        @Override
250        public Collection<DITStructureRule> getDITStuctureRules() {
251            return strictImpl.getDITStuctureRules();
252        }
253
254        @Override
255        public MatchingRule getMatchingRule(final String nameOrOid) {
256            return strictImpl.getMatchingRule(nameOrOid);
257        }
258
259        @Override
260        public Collection<MatchingRule> getMatchingRules() {
261            return strictImpl.getMatchingRules();
262        }
263
264        @Override
265        public Collection<MatchingRule> getMatchingRulesWithName(final String name) {
266            return strictImpl.getMatchingRulesWithName(name);
267        }
268
269        @Override
270        public MatchingRuleUse getMatchingRuleUse(final MatchingRule matchingRule) {
271            return strictImpl.getMatchingRuleUse(matchingRule);
272        }
273
274        @Override
275        public MatchingRuleUse getMatchingRuleUse(final String nameOrOid) {
276            return strictImpl.getMatchingRuleUse(nameOrOid);
277        }
278
279        @Override
280        public Collection<MatchingRuleUse> getMatchingRuleUses() {
281            return strictImpl.getMatchingRuleUses();
282        }
283
284        @Override
285        public Collection<MatchingRuleUse> getMatchingRuleUsesWithName(final String name) {
286            return strictImpl.getMatchingRuleUsesWithName(name);
287        }
288
289        @Override
290        public NameForm getNameForm(final String nameOrOid) {
291            return strictImpl.getNameForm(nameOrOid);
292        }
293
294        @Override
295        public Collection<NameForm> getNameForms() {
296            return strictImpl.getNameForms();
297        }
298
299        @Override
300        public Collection<NameForm> getNameForms(final ObjectClass structuralClass) {
301            return strictImpl.getNameForms(structuralClass);
302        }
303
304        @Override
305        public Collection<NameForm> getNameFormsWithName(final String name) {
306            return strictImpl.getNameFormsWithName(name);
307        }
308
309        @Override
310        public ObjectClass getObjectClass(final String nameOrOid) {
311            ObjectClass result = strictImpl.getObjectClass0(nameOrOid);
312            return result != null ? result : ObjectClass.newPlaceHolder(nameOrOid);
313        }
314
315        @Override
316        public Collection<ObjectClass> getObjectClasses() {
317            return strictImpl.getObjectClasses();
318        }
319
320        @Override
321        public Collection<ObjectClass> getObjectClassesWithName(final String name) {
322            return strictImpl.getObjectClassesWithName(name);
323        }
324
325        @Override
326        public String getSchemaName() {
327            return strictImpl.getSchemaName();
328        }
329
330        @Override
331        public Syntax getSyntax(final Schema schema, final String numericOID) {
332            if (!strictImpl.hasSyntax(numericOID)) {
333                return new Syntax(schema, numericOID);
334            }
335            return strictImpl.getSyntax(schema, numericOID);
336        }
337
338        @Override
339        public Collection<Syntax> getSyntaxes() {
340            return strictImpl.getSyntaxes();
341        }
342
343        @Override
344        public Collection<LocalizableMessage> getWarnings() {
345            return strictImpl.getWarnings();
346        }
347
348        @Override
349        public boolean hasAttributeType(final String nameOrOid) {
350            // In theory a non-strict schema always contains the requested
351            // attribute type, so we could always return true. However, we
352            // should provide a way for callers to differentiate between a
353            // real attribute type and a faked up attribute type.
354            return strictImpl.hasAttributeType(nameOrOid);
355        }
356
357        @Override
358        public boolean hasDITContentRule(final String nameOrOid) {
359            return strictImpl.hasDITContentRule(nameOrOid);
360        }
361
362        @Override
363        public boolean hasDITStructureRule(final int ruleID) {
364            return strictImpl.hasDITStructureRule(ruleID);
365        }
366
367        @Override
368        public boolean hasMatchingRule(final String nameOrOid) {
369            return strictImpl.hasMatchingRule(nameOrOid);
370        }
371
372        @Override
373        public boolean hasMatchingRuleUse(final String nameOrOid) {
374            return strictImpl.hasMatchingRuleUse(nameOrOid);
375        }
376
377        @Override
378        public boolean hasNameForm(final String nameOrOid) {
379            return strictImpl.hasNameForm(nameOrOid);
380        }
381
382        @Override
383        public boolean hasObjectClass(final String nameOrOid) {
384            return strictImpl.hasObjectClass(nameOrOid);
385        }
386
387        @Override
388        public boolean hasSyntax(final String numericOID) {
389            return strictImpl.hasSyntax(numericOID);
390        }
391
392        @Override
393        public boolean isStrict() {
394            return false;
395        }
396    }
397
398    static final class StrictImpl implements Impl {
399        private final Map<Integer, DITStructureRule> id2StructureRules;
400        private final Map<String, List<AttributeType>> name2AttributeTypes;
401        private final Map<String, List<DITContentRule>> name2ContentRules;
402        private final Map<String, List<MatchingRule>> name2MatchingRules;
403        private final Map<String, List<MatchingRuleUse>> name2MatchingRuleUses;
404        private final Map<String, List<NameForm>> name2NameForms;
405        private final Map<String, List<ObjectClass>> name2ObjectClasses;
406        private final Map<String, List<DITStructureRule>> name2StructureRules;
407        private final Map<String, List<DITStructureRule>> nameForm2StructureRules;
408        private final Map<String, AttributeType> numericOID2AttributeTypes;
409        private final Map<String, DITContentRule> numericOID2ContentRules;
410        private final Map<String, MatchingRule> numericOID2MatchingRules;
411        private final Map<String, MatchingRuleUse> numericOID2MatchingRuleUses;
412        private final Map<String, NameForm> numericOID2NameForms;
413        private final Map<String, ObjectClass> numericOID2ObjectClasses;
414        private final Map<String, Syntax> numericOID2Syntaxes;
415        private final Map<String, List<NameForm>> objectClass2NameForms;
416        private final List<LocalizableMessage> warnings;
417        private final String schemaName;
418        private final Options options;
419        private final Syntax defaultSyntax;
420        private final MatchingRule defaultMatchingRule;
421        private final Schema strictSchema;
422        private final Schema nonStrictSchema;
423
424        StrictImpl(final String schemaName,
425                final Options options,
426                final Syntax defaultSyntax,
427                final MatchingRule defaultMatchingRule,
428                final Map<String, Syntax> numericOID2Syntaxes,
429                final Map<String, MatchingRule> numericOID2MatchingRules,
430                final Map<String, MatchingRuleUse> numericOID2MatchingRuleUses,
431                final Map<String, AttributeType> numericOID2AttributeTypes,
432                final Map<String, ObjectClass> numericOID2ObjectClasses,
433                final Map<String, NameForm> numericOID2NameForms,
434                final Map<String, DITContentRule> numericOID2ContentRules,
435                final Map<Integer, DITStructureRule> id2StructureRules,
436                final Map<String, List<MatchingRule>> name2MatchingRules,
437                final Map<String, List<MatchingRuleUse>> name2MatchingRuleUses,
438                final Map<String, List<AttributeType>> name2AttributeTypes,
439                final Map<String, List<ObjectClass>> name2ObjectClasses,
440                final Map<String, List<NameForm>> name2NameForms,
441                final Map<String, List<DITContentRule>> name2ContentRules,
442                final Map<String, List<DITStructureRule>> name2StructureRules,
443                final Map<String, List<NameForm>> objectClass2NameForms,
444                final Map<String, List<DITStructureRule>> nameForm2StructureRules,
445                final List<LocalizableMessage> warnings) {
446            this.schemaName = schemaName;
447            this.options = options;
448            this.defaultSyntax = defaultSyntax;
449            this.defaultMatchingRule = defaultMatchingRule;
450            this.numericOID2Syntaxes = Collections.unmodifiableMap(numericOID2Syntaxes);
451            this.numericOID2MatchingRules = Collections.unmodifiableMap(numericOID2MatchingRules);
452            this.numericOID2MatchingRuleUses = Collections.unmodifiableMap(numericOID2MatchingRuleUses);
453            this.numericOID2AttributeTypes = Collections.unmodifiableMap(numericOID2AttributeTypes);
454            this.numericOID2ObjectClasses = Collections.unmodifiableMap(numericOID2ObjectClasses);
455            this.numericOID2NameForms = Collections.unmodifiableMap(numericOID2NameForms);
456            this.numericOID2ContentRules = Collections.unmodifiableMap(numericOID2ContentRules);
457            this.id2StructureRules = Collections.unmodifiableMap(id2StructureRules);
458            this.name2MatchingRules = Collections.unmodifiableMap(name2MatchingRules);
459            this.name2MatchingRuleUses = Collections.unmodifiableMap(name2MatchingRuleUses);
460            this.name2AttributeTypes = Collections.unmodifiableMap(name2AttributeTypes);
461            this.name2ObjectClasses = Collections.unmodifiableMap(name2ObjectClasses);
462            this.name2NameForms = Collections.unmodifiableMap(name2NameForms);
463            this.name2ContentRules = Collections.unmodifiableMap(name2ContentRules);
464            this.name2StructureRules = Collections.unmodifiableMap(name2StructureRules);
465            this.objectClass2NameForms = Collections.unmodifiableMap(objectClass2NameForms);
466            this.nameForm2StructureRules = Collections.unmodifiableMap(nameForm2StructureRules);
467            this.warnings = Collections.unmodifiableList(warnings);
468            this.strictSchema = new Schema(this);
469            this.nonStrictSchema = new Schema(new NonStrictImpl(this));
470        }
471
472        @Override
473        public Schema asNonStrictSchema() {
474            return nonStrictSchema;
475        }
476
477        @Override
478        public Schema asStrictSchema() {
479            return strictSchema;
480        }
481
482        @Override
483        public Options getOptions() {
484            return options;
485        }
486
487        @Override
488        public Syntax getDefaultSyntax() {
489            return defaultSyntax;
490        }
491
492        @Override
493        public MatchingRule getDefaultMatchingRule() {
494            return defaultMatchingRule;
495        }
496
497        @Override
498        public AttributeType getAttributeType(String nameOrOid, Syntax syntax) {
499            return getAttributeType(null, nameOrOid);
500        }
501
502        @Override
503        public AttributeType getAttributeType(final Schema schema, final String nameOrOid) {
504            final AttributeType type = getAttributeType0(nameOrOid);
505            if (type != null) {
506                return type;
507            }
508            throw new UnknownSchemaElementException(WARN_ATTR_TYPE_UNKNOWN.get(nameOrOid));
509        }
510
511        @Override
512        public Collection<AttributeType> getAttributeTypes() {
513            return numericOID2AttributeTypes.values();
514        }
515
516        @Override
517        public List<AttributeType> getAttributeTypesWithName(final String name) {
518            final List<AttributeType> attributes =
519                    name2AttributeTypes.get(StaticUtils.toLowerCase(name));
520            if (attributes != null) {
521                return attributes;
522            }
523            return Collections.emptyList();
524        }
525
526        @Override
527        public DITContentRule getDITContentRule(final ObjectClass structuralClass) {
528            return numericOID2ContentRules.get(structuralClass.getOID());
529        }
530
531        @Override
532        public DITContentRule getDITContentRule(final String nameOrOid) {
533            final DITContentRule rule = numericOID2ContentRules.get(nameOrOid);
534            if (rule != null) {
535                return rule;
536            }
537            final List<DITContentRule> rules = name2ContentRules.get(StaticUtils.toLowerCase(nameOrOid));
538            if (rules != null) {
539                if (rules.size() == 1) {
540                    return rules.get(0);
541                }
542                throw new UnknownSchemaElementException(WARN_DCR_AMBIGUOUS.get(nameOrOid));
543            }
544            throw new UnknownSchemaElementException(WARN_DCR_UNKNOWN.get(nameOrOid));
545        }
546
547        @Override
548        public Collection<DITContentRule> getDITContentRules() {
549            return numericOID2ContentRules.values();
550        }
551
552        @Override
553        public Collection<DITContentRule> getDITContentRulesWithName(final String name) {
554            final List<DITContentRule> rules = name2ContentRules.get(StaticUtils.toLowerCase(name));
555            if (rules != null) {
556                return rules;
557            }
558            return Collections.emptyList();
559        }
560
561        @Override
562        public DITStructureRule getDITStructureRule(final int ruleID) {
563            final DITStructureRule rule = id2StructureRules.get(ruleID);
564            if (rule == null) {
565                throw new UnknownSchemaElementException(WARN_DSR_UNKNOWN
566                        .get(String.valueOf(ruleID)));
567            }
568            return rule;
569        }
570
571        @Override
572        public Collection<DITStructureRule> getDITStructureRules(final NameForm nameForm) {
573            final List<DITStructureRule> rules = nameForm2StructureRules.get(nameForm.getOID());
574            if (rules != null) {
575                return rules;
576            }
577            return Collections.emptyList();
578        }
579
580        @Override
581        public Collection<DITStructureRule> getDITStructureRulesWithName(final String name) {
582            final List<DITStructureRule> rules =
583                    name2StructureRules.get(StaticUtils.toLowerCase(name));
584            if (rules != null) {
585                return rules;
586            }
587            return Collections.emptyList();
588        }
589
590        @Override
591        public Collection<DITStructureRule> getDITStuctureRules() {
592            return id2StructureRules.values();
593        }
594
595        @Override
596        public MatchingRule getMatchingRule(final String nameOrOid) {
597            final MatchingRule rule = numericOID2MatchingRules.get(nameOrOid);
598            if (rule != null) {
599                return rule;
600            }
601            final List<MatchingRule> rules = name2MatchingRules.get(StaticUtils.toLowerCase(nameOrOid));
602            if (rules != null) {
603                if (rules.size() == 1) {
604                    return rules.get(0);
605                }
606                throw new UnknownSchemaElementException(WARN_MR_AMBIGUOUS.get(nameOrOid));
607            }
608            throw new UnknownSchemaElementException(WARN_MR_UNKNOWN.get(nameOrOid));
609        }
610
611        @Override
612        public Collection<MatchingRule> getMatchingRules() {
613            return numericOID2MatchingRules.values();
614        }
615
616        @Override
617        public Collection<MatchingRule> getMatchingRulesWithName(final String name) {
618            final List<MatchingRule> rules = name2MatchingRules.get(StaticUtils.toLowerCase(name));
619            if (rules != null) {
620                return rules;
621            }
622            return Collections.emptyList();
623        }
624
625        @Override
626        public MatchingRuleUse getMatchingRuleUse(final MatchingRule matchingRule) {
627            return numericOID2MatchingRuleUses.get(matchingRule.getOID());
628        }
629
630        @Override
631        public MatchingRuleUse getMatchingRuleUse(final String nameOrOid) {
632            final MatchingRuleUse rule = numericOID2MatchingRuleUses.get(nameOrOid);
633            if (rule != null) {
634                return rule;
635            }
636            final List<MatchingRuleUse> uses =
637                    name2MatchingRuleUses.get(StaticUtils.toLowerCase(nameOrOid));
638            if (uses != null) {
639                if (uses.size() == 1) {
640                    return uses.get(0);
641                }
642                throw new UnknownSchemaElementException(WARN_MRU_AMBIGUOUS.get(nameOrOid));
643            }
644            throw new UnknownSchemaElementException(WARN_MRU_UNKNOWN.get(nameOrOid));
645        }
646
647        @Override
648        public Collection<MatchingRuleUse> getMatchingRuleUses() {
649            return numericOID2MatchingRuleUses.values();
650        }
651
652        @Override
653        public Collection<MatchingRuleUse> getMatchingRuleUsesWithName(final String name) {
654            final List<MatchingRuleUse> rules =
655                    name2MatchingRuleUses.get(StaticUtils.toLowerCase(name));
656            if (rules != null) {
657                return rules;
658            }
659            return Collections.emptyList();
660        }
661
662        @Override
663        public NameForm getNameForm(final String nameOrOid) {
664            final NameForm form = numericOID2NameForms.get(nameOrOid);
665            if (form != null) {
666                return form;
667            }
668            final List<NameForm> forms = name2NameForms.get(StaticUtils.toLowerCase(nameOrOid));
669            if (forms != null) {
670                if (forms.size() == 1) {
671                    return forms.get(0);
672                }
673                throw new UnknownSchemaElementException(WARN_NAMEFORM_AMBIGUOUS.get(nameOrOid));
674            }
675            throw new UnknownSchemaElementException(WARN_NAMEFORM_UNKNOWN.get(nameOrOid));
676        }
677
678        @Override
679        public Collection<NameForm> getNameForms() {
680            return numericOID2NameForms.values();
681        }
682
683        @Override
684        public Collection<NameForm> getNameForms(final ObjectClass structuralClass) {
685            final List<NameForm> forms = objectClass2NameForms.get(structuralClass.getOID());
686            if (forms != null) {
687                return forms;
688            }
689            return Collections.emptyList();
690        }
691
692        @Override
693        public Collection<NameForm> getNameFormsWithName(final String name) {
694            final List<NameForm> forms = name2NameForms.get(StaticUtils.toLowerCase(name));
695            if (forms != null) {
696                return forms;
697            }
698            return Collections.emptyList();
699        }
700
701        @Override
702        public ObjectClass getObjectClass(final String nameOrOid) {
703            ObjectClass result = getObjectClass0(nameOrOid);
704            if (result != null) {
705                return result;
706            }
707            throw new UnknownSchemaElementException(WARN_OBJECTCLASS_UNKNOWN.get(nameOrOid));
708        }
709
710        private ObjectClass getObjectClass0(final String nameOrOid) {
711            final ObjectClass oc = numericOID2ObjectClasses.get(nameOrOid);
712            if (oc != null) {
713                return oc;
714            }
715            final List<ObjectClass> classes = name2ObjectClasses.get(StaticUtils.toLowerCase(nameOrOid));
716            if (classes != null) {
717                if (classes.size() == 1) {
718                    return classes.get(0);
719                }
720                throw new UnknownSchemaElementException(WARN_OBJECTCLASS_AMBIGUOUS.get(nameOrOid));
721            }
722            return null;
723        }
724
725        @Override
726        public Collection<ObjectClass> getObjectClasses() {
727            return numericOID2ObjectClasses.values();
728        }
729
730        @Override
731        public Collection<ObjectClass> getObjectClassesWithName(final String name) {
732            final List<ObjectClass> classes = name2ObjectClasses.get(StaticUtils.toLowerCase(name));
733            if (classes != null) {
734                return classes;
735            }
736            return Collections.emptyList();
737        }
738
739        @Override
740        public String getSchemaName() {
741            return schemaName;
742        }
743
744        @Override
745        public Syntax getSyntax(final Schema schema, final String numericOID) {
746            final Syntax syntax = numericOID2Syntaxes.get(numericOID);
747            if (syntax == null) {
748                throw new UnknownSchemaElementException(WARN_SYNTAX_UNKNOWN.get(numericOID));
749            }
750            return syntax;
751        }
752
753        @Override
754        public Collection<Syntax> getSyntaxes() {
755            return numericOID2Syntaxes.values();
756        }
757
758        @Override
759        public Collection<LocalizableMessage> getWarnings() {
760            return warnings;
761        }
762
763        @Override
764        public boolean hasAttributeType(final String nameOrOid) {
765            if (numericOID2AttributeTypes.containsKey(nameOrOid)) {
766                return true;
767            }
768            final List<AttributeType> attributes =
769                    name2AttributeTypes.get(StaticUtils.toLowerCase(nameOrOid));
770            return attributes != null && attributes.size() == 1;
771        }
772
773        @Override
774        public boolean hasDITContentRule(final String nameOrOid) {
775            if (numericOID2ContentRules.containsKey(nameOrOid)) {
776                return true;
777            }
778            final List<DITContentRule> rules = name2ContentRules.get(StaticUtils.toLowerCase(nameOrOid));
779            return rules != null && rules.size() == 1;
780        }
781
782        @Override
783        public boolean hasDITStructureRule(final int ruleID) {
784            return id2StructureRules.containsKey(ruleID);
785        }
786
787        @Override
788        public boolean hasMatchingRule(final String nameOrOid) {
789            if (numericOID2MatchingRules.containsKey(nameOrOid)) {
790                return true;
791            }
792            final List<MatchingRule> rules = name2MatchingRules.get(StaticUtils.toLowerCase(nameOrOid));
793            return rules != null && rules.size() == 1;
794        }
795
796        @Override
797        public boolean hasMatchingRuleUse(final String nameOrOid) {
798            if (numericOID2MatchingRuleUses.containsKey(nameOrOid)) {
799                return true;
800            }
801            final List<MatchingRuleUse> uses =
802                    name2MatchingRuleUses.get(StaticUtils.toLowerCase(nameOrOid));
803            return uses != null && uses.size() == 1;
804        }
805
806        @Override
807        public boolean hasNameForm(final String nameOrOid) {
808            if (numericOID2NameForms.containsKey(nameOrOid)) {
809                return true;
810            }
811            final List<NameForm> forms = name2NameForms.get(StaticUtils.toLowerCase(nameOrOid));
812            return forms != null && forms.size() == 1;
813        }
814
815        @Override
816        public boolean hasObjectClass(final String nameOrOid) {
817            if (numericOID2ObjectClasses.containsKey(nameOrOid)) {
818                return true;
819            }
820            final List<ObjectClass> classes = name2ObjectClasses.get(StaticUtils.toLowerCase(nameOrOid));
821            return classes != null && classes.size() == 1;
822        }
823
824        @Override
825        public boolean hasSyntax(final String numericOID) {
826            return numericOID2Syntaxes.containsKey(numericOID);
827        }
828
829        @Override
830        public boolean isStrict() {
831            return true;
832        }
833
834        AttributeType getAttributeType0(final String nameOrOid) {
835            final AttributeType type = numericOID2AttributeTypes.get(nameOrOid);
836            if (type != null) {
837                return type;
838            }
839            final List<AttributeType> attributes =
840                    name2AttributeTypes.get(StaticUtils.toLowerCase(nameOrOid));
841            if (attributes != null) {
842                if (attributes.size() == 1) {
843                    return attributes.get(0);
844                }
845                throw new UnknownSchemaElementException(WARN_ATTR_TYPE_AMBIGUOUS.get(nameOrOid));
846            }
847            return null;
848        }
849    }
850
851    static final String ATTR_ATTRIBUTE_TYPES = "attributeTypes";
852    static final String ATTR_DIT_CONTENT_RULES = "dITContentRules";
853    static final String ATTR_DIT_STRUCTURE_RULES = "dITStructureRules";
854    static final String ATTR_LDAP_SYNTAXES = "ldapSyntaxes";
855    static final String ATTR_MATCHING_RULE_USE = "matchingRuleUse";
856    static final String ATTR_MATCHING_RULES = "matchingRules";
857    static final String ATTR_NAME_FORMS = "nameForms";
858    static final String ATTR_OBJECT_CLASSES = "objectClasses";
859
860    /**
861     * Returns the core schema. The core schema is non-strict and contains the
862     * following standard LDAP schema elements:
863     * <ul>
864     * <li><a href="http://tools.ietf.org/html/rfc4512">RFC 4512 - Lightweight
865     * Directory Access Protocol (LDAP): Directory Information Models </a>
866     * <li><a href="http://tools.ietf.org/html/rfc4517">RFC 4517 - Lightweight
867     * Directory Access Protocol (LDAP): Syntaxes and Matching Rules </a>
868     * <li><a href="http://tools.ietf.org/html/rfc4519">RFC 4519 - Lightweight
869     * Directory Access Protocol (LDAP): Schema for User Applications </a>
870     * <li><a href="http://tools.ietf.org/html/rfc4530">RFC 4530 - Lightweight
871     * Directory Access Protocol (LDAP): entryUUID Operational Attribute </a>
872     * <li><a href="http://tools.ietf.org/html/rfc3045">RFC 3045 - Storing
873     * Vendor Information in the LDAP root DSE </a>
874     * <li><a href="http://tools.ietf.org/html/rfc3112">RFC 3112 - LDAP
875     * Authentication Password Schema </a>
876     * </ul>
877     *
878     * @return The core schema.
879     */
880    public static Schema getCoreSchema() {
881        return CoreSchemaImpl.getInstance();
882    }
883
884    /**
885     * Returns the default schema which should be used by this application. The
886     * default schema is initially set to the core schema.
887     *
888     * @return The default schema which should be used by this application.
889     */
890    public static Schema getDefaultSchema() {
891        return DelayedSchema.defaultSchema;
892    }
893
894    /**
895     * Returns the empty schema. The empty schema is non-strict and does not
896     * contain any schema elements.
897     *
898     * @return The empty schema.
899     */
900    public static Schema getEmptySchema() {
901        return DelayedSchema.EMPTY_SCHEMA;
902    }
903
904    /**
905     * Reads the schema contained in the named subschema sub-entry.
906     * <p>
907     * If the requested schema is not returned by the Directory Server then the
908     * request will fail with an {@link EntryNotFoundException}. More
909     * specifically, this method will never return {@code null}.
910     *
911     * @param connection
912     *            A connection to the Directory Server whose schema is to be
913     *            read.
914     * @param name
915     *            The distinguished name of the subschema sub-entry.
916     * @return The schema from the Directory Server.
917     * @throws LdapException
918     *             If the result code indicates that the request failed for some
919     *             reason.
920     * @throws UnsupportedOperationException
921     *             If the connection does not support search operations.
922     * @throws IllegalStateException
923     *             If the connection has already been closed, i.e. if
924     *             {@code connection.isClosed() == true}.
925     * @throws NullPointerException
926     *             If the {@code connection} or {@code name} was {@code null}.
927     */
928    public static Schema readSchema(final Connection connection, final DN name) throws LdapException {
929        return new SchemaBuilder().addSchema(connection, name, true).toSchema();
930    }
931
932    /**
933     * Asynchronously reads the schema contained in the named subschema
934     * sub-entry.
935     * <p>
936     * If the requested schema is not returned by the Directory Server then the
937     * request will fail with an {@link EntryNotFoundException}. More
938     * specifically, the returned promise will never return {@code null}.
939     *
940     * @param connection
941     *            A connection to the Directory Server whose schema is to be
942     *            read.
943     * @param name
944     *            The distinguished name of the subschema sub-entry.
945     *            the operation result when it is received, may be {@code null}.
946     * @return A promise representing the retrieved schema.
947     * @throws UnsupportedOperationException
948     *             If the connection does not support search operations.
949     * @throws IllegalStateException
950     *             If the connection has already been closed, i.e. if
951     *             {@code connection.isClosed() == true}.
952     * @throws NullPointerException
953     *             If the {@code connection} or {@code name} was {@code null}.
954     */
955    public static LdapPromise<Schema> readSchemaAsync(final Connection connection, final DN name) {
956        final SchemaBuilder builder = new SchemaBuilder();
957        return builder.addSchemaAsync(connection, name, true).then(
958                new Function<SchemaBuilder, Schema, LdapException>() {
959                    @Override
960                    public Schema apply(SchemaBuilder builder) throws LdapException {
961                        return builder.toSchema();
962                    }
963                });
964    }
965
966    /**
967     * Reads the schema contained in the subschema sub-entry which applies to
968     * the named entry.
969     * <p>
970     * If the requested entry or its associated schema are not returned by the
971     * Directory Server then the request will fail with an
972     * {@link EntryNotFoundException}. More specifically, this method will never
973     * return {@code null}.
974     * <p>
975     * This implementation first reads the {@code subschemaSubentry} attribute
976     * of the entry in order to identify the schema and then invokes
977     * {@link #readSchema(Connection, DN)} to read the schema.
978     *
979     * @param connection
980     *            A connection to the Directory Server whose schema is to be
981     *            read.
982     * @param name
983     *            The distinguished name of the entry whose schema is to be
984     *            located.
985     * @return The schema from the Directory Server which applies to the named
986     *         entry.
987     * @throws LdapException
988     *             If the result code indicates that the request failed for some
989     *             reason.
990     * @throws UnsupportedOperationException
991     *             If the connection does not support search operations.
992     * @throws IllegalStateException
993     *             If the connection has already been closed, i.e. if
994     *             {@code connection.isClosed() == true}.
995     * @throws NullPointerException
996     *             If the {@code connection} or {@code name} was {@code null}.
997     */
998    public static Schema readSchemaForEntry(final Connection connection, final DN name)
999            throws LdapException {
1000        return new SchemaBuilder().addSchemaForEntry(connection, name, true).toSchema();
1001    }
1002
1003    /**
1004     * Asynchronously reads the schema contained in the subschema sub-entry
1005     * which applies to the named entry.
1006     * <p>
1007     * If the requested entry or its associated schema are not returned by the
1008     * Directory Server then the request will fail with an
1009     * {@link EntryNotFoundException}. More specifically, the returned promise
1010     * will never return {@code null}.
1011     * <p>
1012     * This implementation first reads the {@code subschemaSubentry} attribute
1013     * of the entry in order to identify the schema and then invokes
1014     * {@link #readSchemaAsync(Connection, DN)} to read the schema.
1015     *
1016     * @param connection
1017     *            A connection to the Directory Server whose schema is to be
1018     *            read.
1019     * @param name
1020     *            The distinguished name of the entry whose schema is to be
1021     *            located.
1022     * @return A promise representing the retrieved schema.
1023     * @throws UnsupportedOperationException
1024     *             If the connection does not support search operations.
1025     * @throws IllegalStateException
1026     *             If the connection has already been closed, i.e. if
1027     *             {@code connection.isClosed() == true}.
1028     * @throws NullPointerException
1029     *             If the {@code connection} or {@code name} was {@code null}.
1030     */
1031    public static LdapPromise<Schema> readSchemaForEntryAsync(final Connection connection, final DN name) {
1032        final SchemaBuilder builder = new SchemaBuilder();
1033        return builder.addSchemaForEntryAsync(connection, name, true).then(
1034            new Function<SchemaBuilder, Schema, LdapException>() {
1035                @Override
1036                public Schema apply(SchemaBuilder builder) throws LdapException {
1037                    return builder.toSchema();
1038                }
1039            });
1040    }
1041
1042    /**
1043     * Sets the default schema which should be used by this application. The
1044     * default schema is initially set to the core schema.
1045     *
1046     * @param schema
1047     *            The default schema which should be used by this application.
1048     */
1049    public static void setDefaultSchema(final Schema schema) {
1050        Reject.ifNull(schema);
1051        DelayedSchema.defaultSchema = schema;
1052    }
1053
1054    /**
1055     * Parses the provided entry as a subschema subentry. Any problems
1056     * encountered while parsing the entry can be retrieved using the returned
1057     * schema's {@link #getWarnings()} method.
1058     *
1059     * @param entry
1060     *            The subschema subentry to be parsed.
1061     * @return The parsed schema.
1062     */
1063    public static Schema valueOf(final Entry entry) {
1064        return new SchemaBuilder(entry).toSchema();
1065    }
1066
1067    private final Impl impl;
1068
1069    Schema(final Impl impl) {
1070        this.impl = impl;
1071    }
1072
1073    /**
1074     * Returns a non-strict view of this schema.
1075     * <p>
1076     * See the description of {@link #isStrict()} for more details.
1077     *
1078     * @return A non-strict view of this schema.
1079     * @see Schema#isStrict()
1080     */
1081    public Schema asNonStrictSchema() {
1082        return impl.asNonStrictSchema();
1083    }
1084
1085    /**
1086     * Returns a strict view of this schema.
1087     * <p>
1088     * See the description of {@link #isStrict()} for more details.
1089     *
1090     * @return A strict view of this schema.
1091     * @see Schema#isStrict()
1092     */
1093    public Schema asStrictSchema() {
1094        return impl.asStrictSchema();
1095    }
1096
1097    MatchingRule getDefaultMatchingRule() {
1098        return impl.getDefaultMatchingRule();
1099    }
1100
1101    Syntax getDefaultSyntax() {
1102        return impl.getDefaultSyntax();
1103    }
1104
1105    /**
1106     * Returns the attribute type for the specified name or numeric OID.
1107     * <p>
1108     * If the requested attribute type is not registered in this schema and this
1109     * schema is non-strict then a temporary "place-holder" attribute type will
1110     * be created and returned. Place holder attribute types have an OID which
1111     * is the normalized attribute name with the string {@code -oid} appended.
1112     * In addition, they will use the directory string syntax and case ignore
1113     * matching rule.
1114     *
1115     * @param nameOrOid
1116     *            The name or OID of the attribute type to retrieve.
1117     * @return The requested attribute type.
1118     * @throws UnknownSchemaElementException
1119     *             If this is a strict schema and the requested attribute type
1120     *             was not found or if the provided name is ambiguous.
1121     * @see AttributeType#isPlaceHolder()
1122     */
1123    public AttributeType getAttributeType(final String nameOrOid) {
1124        return impl.getAttributeType(this, nameOrOid);
1125    }
1126
1127    /**
1128     * Returns the attribute type for the specified name or numeric OID.
1129     * <p>
1130     * If the requested attribute type is not registered in this schema and this
1131     * schema is non-strict then a temporary "place-holder" attribute type will
1132     * be created and returned. Place holder attribute types have an OID which
1133     * is the normalized attribute name with the string {@code -oid} appended.
1134     * In addition, they will use the provided syntax and the default matching
1135     * rule associated with the syntax.
1136     *
1137     * @param nameOrOid
1138     *            The name or OID of the attribute type to retrieve.
1139     * @param syntax
1140     *            The syntax to use when creating the temporary "place-holder" attribute type.
1141     * @return The requested attribute type.
1142     * @throws UnknownSchemaElementException
1143     *             If this is a strict schema and the requested attribute type
1144     *             was not found or if the provided name is ambiguous.
1145     * @see AttributeType#isPlaceHolder()
1146     */
1147    public AttributeType getAttributeType(final String nameOrOid, final Syntax syntax) {
1148        return impl.getAttributeType(nameOrOid, syntax);
1149    }
1150
1151    /**
1152     * Returns an unmodifiable collection containing all of the attribute types
1153     * contained in this schema.
1154     *
1155     * @return An unmodifiable collection containing all of the attribute types
1156     *         contained in this schema.
1157     */
1158    public Collection<AttributeType> getAttributeTypes() {
1159        return impl.getAttributeTypes();
1160    }
1161
1162    /**
1163     * Returns an unmodifiable collection containing all of the attribute types
1164     * having the specified name or numeric OID.
1165     *
1166     * @param name
1167     *            The name of the attribute types to retrieve.
1168     * @return An unmodifiable collection containing all of the attribute types
1169     *         having the specified name or numeric OID.
1170     */
1171    public List<AttributeType> getAttributeTypesWithName(final String name) {
1172        return impl.getAttributeTypesWithName(name);
1173    }
1174
1175    /**
1176     * Returns the DIT content rule associated with the provided structural
1177     * object class, or {@code null} if no rule is defined.
1178     *
1179     * @param structuralClass
1180     *            The structural object class .
1181     * @return The DIT content rule associated with the provided structural
1182     *         object class, or {@code null} if no rule is defined.
1183     */
1184    public DITContentRule getDITContentRule(final ObjectClass structuralClass) {
1185        return impl.getDITContentRule(structuralClass);
1186    }
1187
1188    /**
1189     * Returns the DIT content rule with the specified name or numeric OID.
1190     *
1191     * @param nameOrOid
1192     *            The name or OID of the DIT content rule to retrieve.
1193     * @return The requested DIT content rule.
1194     * @throws UnknownSchemaElementException
1195     *             If this is a strict schema and the requested DIT content rule
1196     *             was not found or if the provided name is ambiguous.
1197     */
1198    public DITContentRule getDITContentRule(final String nameOrOid) {
1199        return impl.getDITContentRule(nameOrOid);
1200    }
1201
1202    /**
1203     * Returns an unmodifiable collection containing all of the DIT content
1204     * rules contained in this schema.
1205     *
1206     * @return An unmodifiable collection containing all of the DIT content
1207     *         rules contained in this schema.
1208     */
1209    public Collection<DITContentRule> getDITContentRules() {
1210        return impl.getDITContentRules();
1211    }
1212
1213    /**
1214     * Returns an unmodifiable collection containing all of the DIT content
1215     * rules having the specified name or numeric OID.
1216     *
1217     * @param name
1218     *            The name of the DIT content rules to retrieve.
1219     * @return An unmodifiable collection containing all of the DIT content
1220     *         rules having the specified name or numeric OID.
1221     */
1222    public Collection<DITContentRule> getDITContentRulesWithName(final String name) {
1223        return impl.getDITContentRulesWithName(name);
1224    }
1225
1226    /**
1227     * Returns the DIT structure rule with the specified name or numeric OID.
1228     *
1229     * @param ruleID
1230     *            The ID of the DIT structure rule to retrieve.
1231     * @return The requested DIT structure rule.
1232     * @throws UnknownSchemaElementException
1233     *             If this is a strict schema and the requested DIT structure
1234     *             rule was not found.
1235     */
1236    public DITStructureRule getDITStructureRule(final int ruleID) {
1237        return impl.getDITStructureRule(ruleID);
1238    }
1239
1240    /**
1241     * Returns an unmodifiable collection containing all of the DIT structure
1242     * rules associated with the provided name form.
1243     *
1244     * @param nameForm
1245     *            The name form.
1246     * @return An unmodifiable collection containing all of the DIT structure
1247     *         rules associated with the provided name form.
1248     */
1249    public Collection<DITStructureRule> getDITStructureRules(final NameForm nameForm) {
1250        return impl.getDITStructureRules(nameForm);
1251    }
1252
1253    /**
1254     * Returns an unmodifiable collection containing all of the DIT structure
1255     * rules having the specified name or numeric OID.
1256     *
1257     * @param name
1258     *            The name of the DIT structure rules to retrieve.
1259     * @return An unmodifiable collection containing all of the DIT structure
1260     *         rules having the specified name or numeric OID.
1261     */
1262    public Collection<DITStructureRule> getDITStructureRulesWithName(final String name) {
1263        return impl.getDITStructureRulesWithName(name);
1264    }
1265
1266    /**
1267     * Returns an unmodifiable collection containing all of the DIT structure
1268     * rules contained in this schema.
1269     *
1270     * @return An unmodifiable collection containing all of the DIT structure
1271     *         rules contained in this schema.
1272     */
1273    public Collection<DITStructureRule> getDITStuctureRules() {
1274        return impl.getDITStuctureRules();
1275    }
1276
1277    /**
1278     * Returns the matching rule with the specified name or numeric OID.
1279     *
1280     * @param nameOrOid
1281     *            The name or OID of the matching rule to retrieve.
1282     * @return The requested matching rule.
1283     * @throws UnknownSchemaElementException
1284     *             If this is a strict schema and the requested matching rule
1285     *             was not found or if the provided name is ambiguous.
1286     */
1287    public MatchingRule getMatchingRule(final String nameOrOid) {
1288        return impl.getMatchingRule(nameOrOid);
1289    }
1290
1291    /**
1292     * Returns an unmodifiable collection containing all of the matching rules
1293     * contained in this schema.
1294     *
1295     * @return An unmodifiable collection containing all of the matching rules
1296     *         contained in this schema.
1297     */
1298    public Collection<MatchingRule> getMatchingRules() {
1299        return impl.getMatchingRules();
1300    }
1301
1302    /**
1303     * Returns an unmodifiable collection containing all of the matching rules
1304     * having the specified name or numeric OID.
1305     *
1306     * @param name
1307     *            The name of the matching rules to retrieve.
1308     * @return An unmodifiable collection containing all of the matching rules
1309     *         having the specified name or numeric OID.
1310     */
1311    public Collection<MatchingRule> getMatchingRulesWithName(final String name) {
1312        return impl.getMatchingRulesWithName(name);
1313    }
1314
1315    /**
1316     * Returns the matching rule use associated with the provided matching rule,
1317     * or {@code null} if no use is defined.
1318     *
1319     * @param matchingRule
1320     *            The matching rule whose matching rule use is to be retrieved.
1321     * @return The matching rule use associated with the provided matching rule,
1322     *         or {@code null} if no use is defined.
1323     */
1324    public MatchingRuleUse getMatchingRuleUse(final MatchingRule matchingRule) {
1325        return getMatchingRuleUse(matchingRule.getOID());
1326    }
1327
1328    /**
1329     * Returns the matching rule use with the specified name or numeric OID.
1330     *
1331     * @param nameOrOid
1332     *            The name or OID of the matching rule use to retrieve.
1333     * @return The requested matching rule use.
1334     * @throws UnknownSchemaElementException
1335     *             If this is a strict schema and the requested matching rule
1336     *             use was not found or if the provided name is ambiguous.
1337     */
1338    public MatchingRuleUse getMatchingRuleUse(final String nameOrOid) {
1339        return impl.getMatchingRuleUse(nameOrOid);
1340    }
1341
1342    /**
1343     * Returns an unmodifiable collection containing all of the matching rule
1344     * uses contained in this schema.
1345     *
1346     * @return An unmodifiable collection containing all of the matching rule
1347     *         uses contained in this schema.
1348     */
1349    public Collection<MatchingRuleUse> getMatchingRuleUses() {
1350        return impl.getMatchingRuleUses();
1351    }
1352
1353    /**
1354     * Returns an unmodifiable collection containing all of the matching rule
1355     * uses having the specified name or numeric OID.
1356     *
1357     * @param name
1358     *            The name of the matching rule uses to retrieve.
1359     * @return An unmodifiable collection containing all of the matching rule
1360     *         uses having the specified name or numeric OID.
1361     */
1362    public Collection<MatchingRuleUse> getMatchingRuleUsesWithName(final String name) {
1363        return impl.getMatchingRuleUsesWithName(name);
1364    }
1365
1366    /**
1367     * Returns the name form with the specified name or numeric OID.
1368     *
1369     * @param nameOrOid
1370     *            The name or OID of the name form to retrieve.
1371     * @return The requested name form.
1372     * @throws UnknownSchemaElementException
1373     *             If this is a strict schema and the requested name form was
1374     *             not found or if the provided name is ambiguous.
1375     */
1376    public NameForm getNameForm(final String nameOrOid) {
1377        return impl.getNameForm(nameOrOid);
1378    }
1379
1380    /**
1381     * Returns an unmodifiable collection containing all of the name forms
1382     * contained in this schema.
1383     *
1384     * @return An unmodifiable collection containing all of the name forms
1385     *         contained in this schema.
1386     */
1387    public Collection<NameForm> getNameForms() {
1388        return impl.getNameForms();
1389    }
1390
1391    /**
1392     * Returns an unmodifiable collection containing all of the name forms
1393     * associated with the provided structural object class.
1394     *
1395     * @param structuralClass
1396     *            The structural object class whose name forms are to be
1397     *            retrieved.
1398     * @return An unmodifiable collection containing all of the name forms
1399     *         associated with the provided structural object class.
1400     */
1401    public Collection<NameForm> getNameForms(final ObjectClass structuralClass) {
1402        return impl.getNameForms(structuralClass);
1403    }
1404
1405    /**
1406     * Returns an unmodifiable collection containing all of the name forms
1407     * having the specified name or numeric OID.
1408     *
1409     * @param name
1410     *            The name of the name forms to retrieve.
1411     * @return An unmodifiable collection containing all of the name forms
1412     *         having the specified name or numeric OID.
1413     */
1414    public Collection<NameForm> getNameFormsWithName(final String name) {
1415        return impl.getNameFormsWithName(name);
1416    }
1417
1418    /**
1419     * Returns the object class with the specified name or numeric OID.
1420     * <p>
1421     * If the requested object class is not registered in this schema and this
1422     * schema is non-strict then a temporary "place-holder" object class will
1423     * be created and returned. Place holder object classes have an OID which
1424     * is the normalized name with the string {@code -oid} appended.
1425     *
1426     * @param nameOrOid
1427     *            The name or OID of the object class to retrieve.
1428     * @return The requested object class.
1429     * @throws UnknownSchemaElementException
1430     *             If this is a strict schema and the requested object class was
1431     *             not found or if the provided name is ambiguous.
1432     * @see ObjectClass#isPlaceHolder()
1433     */
1434    public ObjectClass getObjectClass(final String nameOrOid) {
1435        return impl.getObjectClass(nameOrOid);
1436    }
1437
1438    /**
1439     * Returns an unmodifiable collection containing all of the object classes
1440     * contained in this schema.
1441     *
1442     * @return An unmodifiable collection containing all of the object classes
1443     *         contained in this schema.
1444     */
1445    public Collection<ObjectClass> getObjectClasses() {
1446        return impl.getObjectClasses();
1447    }
1448
1449    /**
1450     * Returns an unmodifiable collection containing all of the object classes
1451     * having the specified name or numeric OID.
1452     *
1453     * @param name
1454     *            The name of the object classes to retrieve.
1455     * @return An unmodifiable collection containing all of the object classes
1456     *         having the specified name or numeric OID.
1457     */
1458    public Collection<ObjectClass> getObjectClassesWithName(final String name) {
1459        return impl.getObjectClassesWithName(name);
1460    }
1461
1462    /**
1463     * Returns the value associated to the provided {@link Option} or the option
1464     * default value, if there is no such option in this schema.
1465     *
1466     * @param <T>
1467     *            The option type.
1468     * @param option
1469     *            The option whose associated value should to be retrieve.
1470     * @return The value associated to the provided {@link Option} or the option
1471     *         default value, if there is no such option in this schema.
1472     */
1473    public <T> T getOption(Option<T> option) {
1474        return getOptions().get(option);
1475    }
1476
1477    Options getOptions() {
1478        return impl.getOptions();
1479    }
1480
1481    /**
1482     * Returns the user-friendly name of this schema which may be used for
1483     * debugging purposes. The format of the schema name is not defined but
1484     * should contain the distinguished name of the subschema sub-entry for
1485     * those schemas retrieved from a Directory Server.
1486     *
1487     * @return The user-friendly name of this schema which may be used for
1488     *         debugging purposes.
1489     */
1490    public String getSchemaName() {
1491        return impl.getSchemaName();
1492    }
1493
1494    /**
1495     * Returns the syntax with the specified numeric OID.
1496     *
1497     * @param numericOID
1498     *            The OID of the syntax to retrieve.
1499     * @return The requested syntax.
1500     * @throws UnknownSchemaElementException
1501     *             If this is a strict schema and the requested syntax was not
1502     *             found or if the provided name is ambiguous.
1503     */
1504    public Syntax getSyntax(final String numericOID) {
1505        return impl.getSyntax(this, numericOID);
1506    }
1507
1508    /**
1509     * Returns an unmodifiable collection containing all of the syntaxes
1510     * contained in this schema.
1511     *
1512     * @return An unmodifiable collection containing all of the syntaxes
1513     *         contained in this schema.
1514     */
1515    public Collection<Syntax> getSyntaxes() {
1516        return impl.getSyntaxes();
1517    }
1518
1519    /**
1520     * Returns an unmodifiable collection containing all of the warnings that
1521     * were detected when this schema was constructed.
1522     *
1523     * @return An unmodifiable collection containing all of the warnings that
1524     *         were detected when this schema was constructed.
1525     */
1526    public Collection<LocalizableMessage> getWarnings() {
1527        return impl.getWarnings();
1528    }
1529
1530    /**
1531     * Indicates whether this schema contains an attribute type with the
1532     * specified name or numeric OID.
1533     *
1534     * @param nameOrOid
1535     *            The name or OID of the attribute type.
1536     * @return {@code true} if this schema contains an attribute type with the
1537     *         specified name or numeric OID, otherwise {@code false}.
1538     */
1539    public boolean hasAttributeType(final String nameOrOid) {
1540        return impl.hasAttributeType(nameOrOid);
1541    }
1542
1543    /**
1544     * Indicates whether this schema contains a DIT content rule with the
1545     * specified name or numeric OID.
1546     *
1547     * @param nameOrOid
1548     *            The name or OID of the DIT content rule.
1549     * @return {@code true} if this schema contains a DIT content rule with the
1550     *         specified name or numeric OID, otherwise {@code false}.
1551     */
1552    public boolean hasDITContentRule(final String nameOrOid) {
1553        return impl.hasDITContentRule(nameOrOid);
1554    }
1555
1556    /**
1557     * Indicates whether this schema contains a DIT structure rule with
1558     * the specified rule ID.
1559     *
1560     * @param ruleID
1561     *            The ID of the DIT structure rule.
1562     * @return {@code true} if this schema contains a DIT structure rule with
1563     *         the specified rule ID, otherwise {@code false}.
1564     */
1565    public boolean hasDITStructureRule(final int ruleID) {
1566        return impl.hasDITStructureRule(ruleID);
1567    }
1568
1569    /**
1570     * Indicates whether this schema contains a matching rule with the
1571     * specified name or numeric OID.
1572     *
1573     * @param nameOrOid
1574     *            The name or OID of the matching rule.
1575     * @return {@code true} if this schema contains a matching rule with the
1576     *         specified name or numeric OID, otherwise {@code false}.
1577     */
1578    public boolean hasMatchingRule(final String nameOrOid) {
1579        return impl.hasMatchingRule(nameOrOid);
1580    }
1581
1582    /**
1583     * Indicates whether this schema contains a matching rule use with
1584     * the specified name or numeric OID.
1585     *
1586     * @param nameOrOid
1587     *            The name or OID of the matching rule use.
1588     * @return {@code true} if this schema contains a matching rule use with the
1589     *         specified name or numeric OID, otherwise {@code false}.
1590     */
1591    public boolean hasMatchingRuleUse(final String nameOrOid) {
1592        return impl.hasMatchingRuleUse(nameOrOid);
1593    }
1594
1595    /**
1596     * Indicates whether this schema contains a name form with the
1597     * specified name or numeric OID.
1598     *
1599     * @param nameOrOid
1600     *            The name or OID of the name form.
1601     * @return {@code true} if this schema contains a name form with the
1602     *         specified name or numeric OID, otherwise {@code false}.
1603     */
1604    public boolean hasNameForm(final String nameOrOid) {
1605        return impl.hasNameForm(nameOrOid);
1606    }
1607
1608    /**
1609     * Indicates whether this schema contains an object class with the
1610     * specified name or numeric OID.
1611     *
1612     * @param nameOrOid
1613     *            The name or OID of the object class.
1614     * @return {@code true} if this schema contains an object class with the
1615     *         specified name or numeric OID, otherwise {@code false}.
1616     */
1617    public boolean hasObjectClass(final String nameOrOid) {
1618        return impl.hasObjectClass(nameOrOid);
1619    }
1620
1621    /**
1622     * Indicates whether this schema contains a syntax with the specified
1623     * numeric OID.
1624     *
1625     * @param numericOID
1626     *            The OID of the syntax.
1627     * @return {@code true} if this schema contains a syntax with the specified
1628     *         numeric OID, otherwise {@code false}.
1629     */
1630    public boolean hasSyntax(final String numericOID) {
1631        return impl.hasSyntax(numericOID);
1632    }
1633
1634    /**
1635     * Indicates whether this schema is strict.
1636     * <p>
1637     * Attribute type queries against non-strict schema always succeed: if the
1638     * requested attribute type is not found then a temporary attribute type is
1639     * created automatically having the Octet String syntax and associated
1640     * matching rules.
1641     * <p>
1642     * Strict schema, on the other hand, throw an
1643     * {@link UnknownSchemaElementException} whenever an attempt is made to
1644     * retrieve a non-existent attribute type.
1645     *
1646     * @return {@code true} if this schema is strict.
1647     */
1648    public boolean isStrict() {
1649        return impl.isStrict();
1650    }
1651
1652    /**
1653     * Adds the definitions of all the schema elements contained in this schema
1654     * to the provided subschema subentry. Any existing attributes (including
1655     * schema definitions) contained in the provided entry will be preserved.
1656     *
1657     * @param entry
1658     *            The subschema subentry to which all schema definitions should
1659     *            be added.
1660     * @return The updated subschema subentry.
1661     * @throws NullPointerException
1662     *             If {@code entry} was {@code null}.
1663     */
1664    public Entry toEntry(final Entry entry) {
1665        addAttribute(entry, Schema.ATTR_LDAP_SYNTAXES, getSyntaxes());
1666        addAttribute(entry, Schema.ATTR_ATTRIBUTE_TYPES, getAttributeTypes());
1667        addAttribute(entry, Schema.ATTR_OBJECT_CLASSES, getObjectClasses());
1668        addAttribute(entry, Schema.ATTR_MATCHING_RULE_USE, getMatchingRuleUses());
1669        addAttribute(entry, Schema.ATTR_MATCHING_RULES, getMatchingRules());
1670        addAttribute(entry, Schema.ATTR_DIT_CONTENT_RULES, getDITContentRules());
1671        addAttribute(entry, Schema.ATTR_DIT_STRUCTURE_RULES, getDITStuctureRules());
1672        addAttribute(entry, Schema.ATTR_NAME_FORMS, getNameForms());
1673        return entry;
1674    }
1675
1676    private void addAttribute(Entry entry, String attrName, Collection<? extends SchemaElement> schemaElements) {
1677        Attribute attr = new LinkedAttribute(attrName);
1678        for (final Object o : schemaElements) {
1679            attr.add(o.toString());
1680        }
1681        if (!attr.isEmpty()) {
1682            entry.addAttribute(attr);
1683        }
1684    }
1685
1686    /**
1687     * Returns {@code true} if the provided entry is valid according to this
1688     * schema and the specified schema validation policy.
1689     * <p>
1690     * If attribute value validation is enabled then following checks will be
1691     * performed:
1692     * <ul>
1693     * <li>checking that there is at least one value
1694     * <li>checking that single-valued attributes contain only a single value
1695     * </ul>
1696     * In particular, attribute values will not be checked for conformance to
1697     * their syntax since this is expected to have already been performed while
1698     * adding the values to the entry.
1699     *
1700     * @param entry
1701     *            The entry to be validated.
1702     * @param policy
1703     *            The schema validation policy.
1704     * @param errorMessages
1705     *            A collection into which any schema validation warnings or
1706     *            error messages can be placed, or {@code null} if they should
1707     *            not be saved.
1708     * @return {@code true} if an entry conforms to this schema based on the
1709     *         provided schema validation policy.
1710     */
1711    public boolean validateEntry(final Entry entry, final SchemaValidationPolicy policy,
1712            final Collection<LocalizableMessage> errorMessages) {
1713        // First check that the object classes are recognized and that there is
1714        // one structural object class.
1715        ObjectClass structuralObjectClass = null;
1716        final Attribute objectClassAttribute = entry.getAttribute(objectClass());
1717        final List<ObjectClass> objectClasses = new LinkedList<>();
1718        if (objectClassAttribute != null) {
1719            for (final ByteString v : objectClassAttribute) {
1720                final String objectClassName = v.toString();
1721                final ObjectClass objectClass;
1722                try {
1723                    objectClass = asStrictSchema().getObjectClass(objectClassName);
1724                    objectClasses.add(objectClass);
1725                } catch (final UnknownSchemaElementException e) {
1726                    if (policy.checkAttributesAndObjectClasses().needsChecking()) {
1727                        if (errorMessages != null) {
1728                            errorMessages.add(ERR_ENTRY_SCHEMA_UNKNOWN_OBJECT_CLASS.get(
1729                                    entry.getName(), objectClassName));
1730                        }
1731                        if (policy.checkAttributesAndObjectClasses().isReject()) {
1732                            return false;
1733                        }
1734                    }
1735                    continue;
1736                }
1737
1738                if (objectClass.getObjectClassType() == ObjectClassType.STRUCTURAL) {
1739                    if (structuralObjectClass == null
1740                            || objectClass.isDescendantOf(structuralObjectClass)) {
1741                        structuralObjectClass = objectClass;
1742                    } else if (!structuralObjectClass.isDescendantOf(objectClass)
1743                            && policy.requireSingleStructuralObjectClass().needsChecking()) {
1744                        if (errorMessages != null) {
1745                            errorMessages.add(ERR_ENTRY_SCHEMA_MULTIPLE_STRUCTURAL_CLASSES.get(
1746                                    entry.getName(), structuralObjectClass.getNameOrOID(), objectClassName));
1747                        }
1748                        if (policy.requireSingleStructuralObjectClass().isReject()) {
1749                            return false;
1750                        }
1751                    }
1752                }
1753            }
1754        }
1755
1756        Collection<DITStructureRule> ditStructureRules = Collections.emptyList();
1757        DITContentRule ditContentRule = null;
1758
1759        if (structuralObjectClass == null) {
1760            if (policy.requireSingleStructuralObjectClass().needsChecking()) {
1761                if (errorMessages != null) {
1762                    errorMessages.add(ERR_ENTRY_SCHEMA_NO_STRUCTURAL_CLASS.get(entry.getName()));
1763                }
1764                if (policy.requireSingleStructuralObjectClass().isReject()) {
1765                    return false;
1766                }
1767            }
1768        } else {
1769            ditContentRule = getDITContentRule(structuralObjectClass);
1770            if (ditContentRule != null && ditContentRule.isObsolete()) {
1771                ditContentRule = null;
1772            }
1773        }
1774
1775        // Check entry conforms to object classes and optional content rule.
1776        if (!checkAttributesAndObjectClasses(entry, policy, errorMessages, objectClasses,
1777                ditContentRule)) {
1778            return false;
1779        }
1780
1781        // Check that the name of the entry conforms to at least one applicable
1782        // name form.
1783        if (policy.checkNameForms().needsChecking() && structuralObjectClass != null) {
1784            /**
1785             * There may be multiple name forms registered with this structural
1786             * object class. However, we need to select only one of the name
1787             * forms and its corresponding DIT structure rule(s). We will
1788             * iterate over all the name forms and see if at least one is
1789             * acceptable before rejecting the entry. DIT structure rules
1790             * corresponding to other non-acceptable name forms are not applied.
1791             */
1792            boolean foundMatchingNameForms = false;
1793            NameForm nameForm = null;
1794            final List<LocalizableMessage> nameFormWarnings =
1795                    (errorMessages != null) ? new LinkedList<LocalizableMessage>() : null;
1796            for (final NameForm nf : getNameForms(structuralObjectClass)) {
1797                if (nf.isObsolete()) {
1798                    continue;
1799                }
1800
1801                // If there are any candidate name forms then at least one
1802                // should be valid.
1803                foundMatchingNameForms = true;
1804
1805                if (checkNameForm(entry, nameFormWarnings, nf)) {
1806                    nameForm = nf;
1807                    break;
1808                }
1809            }
1810
1811            if (foundMatchingNameForms) {
1812                if (nameForm != null) {
1813                    ditStructureRules = getDITStructureRules(nameForm);
1814                } else {
1815                    // We couldn't match this entry against any of the name
1816                    // forms, so append the reasons why they didn't match and
1817                    // reject if required.
1818                    if (errorMessages != null) {
1819                        errorMessages.addAll(nameFormWarnings);
1820                    }
1821                    if (policy.checkNameForms().isReject()) {
1822                        return false;
1823                    }
1824                }
1825            }
1826        }
1827
1828        // Check DIT structure rules - this needs the parent entry.
1829        if (policy.checkDITStructureRules().needsChecking() && !entry.getName().isRootDN()) {
1830            boolean foundMatchingRules = false;
1831            boolean foundValidRule = false;
1832            final List<LocalizableMessage> ruleWarnings =
1833                    (errorMessages != null) ? new LinkedList<LocalizableMessage>() : null;
1834            ObjectClass parentStructuralObjectClass = null;
1835            boolean parentEntryHasBeenRead = false;
1836            for (final DITStructureRule rule : ditStructureRules) {
1837                if (rule.isObsolete()) {
1838                    continue;
1839                }
1840
1841                foundMatchingRules = true;
1842
1843                // A DIT structure rule with no superiors is automatically
1844                // valid, so avoid reading the parent.
1845                if (rule.getSuperiorRules().isEmpty()) {
1846                    foundValidRule = true;
1847                    break;
1848                }
1849
1850                if (!parentEntryHasBeenRead) {
1851                    // Don't drop out immediately on failure because there may
1852                    // be some
1853                    // applicable rules which do not require the parent entry.
1854                    parentStructuralObjectClass =
1855                            getParentStructuralObjectClass(entry, policy, ruleWarnings);
1856                    parentEntryHasBeenRead = true;
1857                }
1858
1859                if (parentStructuralObjectClass != null
1860                      && checkDITStructureRule(entry, ruleWarnings, rule,
1861                          structuralObjectClass, parentStructuralObjectClass)) {
1862                    foundValidRule = true;
1863                    break;
1864                }
1865            }
1866
1867            if (foundMatchingRules) {
1868                if (!foundValidRule) {
1869                    // We couldn't match this entry against any of the rules, so
1870                    // append the reasons why they didn't match and reject if
1871                    // required.
1872                    if (errorMessages != null) {
1873                        errorMessages.addAll(ruleWarnings);
1874                    }
1875                    if (policy.checkDITStructureRules().isReject()) {
1876                        return false;
1877                    }
1878                }
1879            } else {
1880                // There is no DIT structure rule for this entry, but there may
1881                // be one for the parent entry. If there is such a rule for the
1882                // parent entry, then this entry will not be valid.
1883
1884                // The parent won't have been read yet.
1885                parentStructuralObjectClass =
1886                        getParentStructuralObjectClass(entry, policy, ruleWarnings);
1887                if (parentStructuralObjectClass == null) {
1888                    if (errorMessages != null) {
1889                        errorMessages.addAll(ruleWarnings);
1890                    }
1891                    if (policy.checkDITStructureRules().isReject()) {
1892                        return false;
1893                    }
1894                } else {
1895                    for (final NameForm nf : getNameForms(parentStructuralObjectClass)) {
1896                        if (!nf.isObsolete()) {
1897                            for (final DITStructureRule rule : getDITStructureRules(nf)) {
1898                                if (!rule.isObsolete()) {
1899                                    if (errorMessages != null) {
1900                                        errorMessages.add(ERR_ENTRY_SCHEMA_DSR_MISSING_DSR.get(
1901                                                entry.getName(), rule.getNameOrRuleID()));
1902                                    }
1903                                    if (policy.checkDITStructureRules().isReject()) {
1904                                        return false;
1905                                    }
1906
1907                                    // We could break out of the loop here in
1908                                    // warn mode but continuing allows us to
1909                                    // collect all conflicts.
1910                                }
1911                            }
1912                        }
1913                    }
1914                }
1915            }
1916        }
1917
1918        // If we've gotten here, then the entry is acceptable.
1919        return true;
1920    }
1921
1922    private boolean checkAttributesAndObjectClasses(final Entry entry,
1923            final SchemaValidationPolicy policy,
1924            final Collection<LocalizableMessage> errorMessages,
1925            final List<ObjectClass> objectClasses, final DITContentRule ditContentRule) {
1926        // Check object classes.
1927        final boolean checkDITContentRule =
1928                policy.checkDITContentRules().needsChecking() && ditContentRule != null;
1929        final boolean checkObjectClasses = policy.checkAttributesAndObjectClasses().needsChecking();
1930        final boolean checkAttributeValues = policy.checkAttributeValues().needsChecking();
1931
1932        if (checkObjectClasses || checkDITContentRule) {
1933            for (final ObjectClass objectClass : objectClasses) {
1934                // Make sure that any auxiliary object classes are permitted by
1935                // the content rule.
1936                if (checkDITContentRule
1937                        && objectClass.getObjectClassType() == ObjectClassType.AUXILIARY
1938                        && !ditContentRule.getAuxiliaryClasses().contains(objectClass)) {
1939                    if (errorMessages != null) {
1940                        errorMessages.add(ERR_ENTRY_SCHEMA_DCR_PROHIBITED_AUXILIARY_OC.get(
1941                                entry.getName(), objectClass.getNameOrOID(), ditContentRule.getNameOrOID()));
1942                    }
1943                    if (policy.checkDITContentRules().isReject()) {
1944                        return false;
1945                    }
1946                }
1947
1948                // Make sure that all of the attributes required by the object
1949                // class are present.
1950                if (checkObjectClasses) {
1951                    for (final AttributeType t : objectClass.getDeclaredRequiredAttributes()) {
1952                        final Attribute a =
1953                                Attributes.emptyAttribute(AttributeDescription.create(t));
1954                        if (!entry.containsAttribute(a, null)) {
1955                            if (errorMessages != null) {
1956                                errorMessages.add(ERR_ENTRY_SCHEMA_OC_MISSING_MUST_ATTRIBUTES.get(
1957                                        entry.getName(), t.getNameOrOID(), objectClass.getNameOrOID()));
1958                            }
1959                            if (policy.checkAttributesAndObjectClasses().isReject()) {
1960                                return false;
1961                            }
1962                        }
1963                    }
1964                }
1965            }
1966
1967            // Make sure that all of the attributes required by the content rule
1968            // are present.
1969            if (checkDITContentRule) {
1970                for (final AttributeType t : ditContentRule.getRequiredAttributes()) {
1971                    final Attribute a = Attributes.emptyAttribute(AttributeDescription.create(t));
1972                    if (!entry.containsAttribute(a, null)) {
1973                        if (errorMessages != null) {
1974                            errorMessages.add(ERR_ENTRY_SCHEMA_DCR_MISSING_MUST_ATTRIBUTES.get(
1975                                    entry.getName(), t.getNameOrOID(), ditContentRule.getNameOrOID()));
1976                        }
1977                        if (policy.checkDITContentRules().isReject()) {
1978                            return false;
1979                        }
1980                    }
1981                }
1982
1983                // Make sure that attributes prohibited by the content rule are
1984                // not present.
1985                for (final AttributeType t : ditContentRule.getProhibitedAttributes()) {
1986                    final Attribute a = Attributes.emptyAttribute(AttributeDescription.create(t));
1987                    if (entry.containsAttribute(a, null)) {
1988                        if (errorMessages != null) {
1989                            errorMessages.add(ERR_ENTRY_SCHEMA_DCR_PROHIBITED_ATTRIBUTES.get(
1990                                    entry.getName(), t.getNameOrOID(), ditContentRule.getNameOrOID()));
1991                        }
1992                        if (policy.checkDITContentRules().isReject()) {
1993                            return false;
1994                        }
1995                    }
1996                }
1997            }
1998        }
1999
2000        // Check attributes.
2001        if (checkObjectClasses || checkDITContentRule || checkAttributeValues) {
2002            for (final Attribute attribute : entry.getAllAttributes()) {
2003                final AttributeType t = attribute.getAttributeDescription().getAttributeType();
2004
2005                if (!t.isOperational()
2006                        && (checkObjectClasses || checkDITContentRule)) {
2007                    boolean isAllowed = isRequiredOrOptional(objectClasses, t);
2008                    if (!isAllowed && ditContentRule != null && ditContentRule.isRequiredOrOptional(t)) {
2009                        isAllowed = true;
2010                    }
2011                    if (!isAllowed) {
2012                        if (errorMessages != null) {
2013                            final LocalizableMessage message = ditContentRule != null
2014                                ? ERR_ENTRY_SCHEMA_DCR_DISALLOWED_ATTRIBUTES.get(
2015                                        entry.getName(), t.getNameOrOID(), ditContentRule.getNameOrOID())
2016                                : ERR_ENTRY_SCHEMA_OC_DISALLOWED_ATTRIBUTES.get(entry.getName(), t.getNameOrOID());
2017                            errorMessages.add(message);
2018                        }
2019                        if (policy.checkAttributesAndObjectClasses().isReject()
2020                                || policy.checkDITContentRules().isReject()) {
2021                            return false;
2022                        }
2023                    }
2024                }
2025
2026                // Check all attributes contain an appropriate number of values.
2027                if (checkAttributeValues) {
2028                    final int sz = attribute.size();
2029                    if (sz == 0) {
2030                        if (errorMessages != null) {
2031                            errorMessages.add(ERR_ENTRY_SCHEMA_AT_EMPTY_ATTRIBUTE.get(
2032                                    entry.getName(), t.getNameOrOID()));
2033                        }
2034                        if (policy.checkAttributeValues().isReject()) {
2035                            return false;
2036                        }
2037                    } else if (sz > 1 && t.isSingleValue()) {
2038                        if (errorMessages != null) {
2039                            errorMessages.add(ERR_ENTRY_SCHEMA_AT_SINGLE_VALUED_ATTRIBUTE.get(
2040                                    entry.getName(), t.getNameOrOID()));
2041                        }
2042                        if (policy.checkAttributeValues().isReject()) {
2043                            return false;
2044                        }
2045                    }
2046                }
2047            }
2048        }
2049
2050        // If we've gotten here, then things are OK.
2051        return true;
2052    }
2053
2054    private boolean isRequiredOrOptional(final List<ObjectClass> objectClasses, final AttributeType t) {
2055        for (final ObjectClass objectClass : objectClasses) {
2056            if (objectClass.isRequiredOrOptional(t)) {
2057                return true;
2058            }
2059        }
2060        return false;
2061    }
2062
2063    private boolean checkDITStructureRule(final Entry entry,
2064            final List<LocalizableMessage> ruleWarnings, final DITStructureRule rule,
2065            final ObjectClass structuralObjectClass, final ObjectClass parentStructuralObjectClass) {
2066        boolean matchFound = false;
2067        for (final DITStructureRule parentRule : rule.getSuperiorRules()) {
2068            if (parentRule.getNameForm().getStructuralClass().equals(parentStructuralObjectClass)) {
2069                matchFound = true;
2070            }
2071        }
2072
2073        if (!matchFound) {
2074            if (ruleWarnings != null) {
2075                ruleWarnings.add(ERR_ENTRY_SCHEMA_DSR_ILLEGAL_OC.get(
2076                        entry.getName(), rule.getNameOrRuleID(), structuralObjectClass.getNameOrOID(),
2077                        parentStructuralObjectClass.getNameOrOID()));
2078            }
2079            return false;
2080        }
2081
2082        return true;
2083    }
2084
2085    private boolean checkNameForm(final Entry entry, final List<LocalizableMessage> nameFormWarnings,
2086            final NameForm nameForm) {
2087        final RDN rdn = entry.getName().rdn();
2088        if (rdn != null) {
2089            // Make sure that all the required AVAs are present.
2090            for (final AttributeType t : nameForm.getRequiredAttributes()) {
2091                if (rdn.getAttributeValue(t) == null) {
2092                    if (nameFormWarnings != null) {
2093                        nameFormWarnings.add(ERR_ENTRY_SCHEMA_NF_MISSING_MUST_ATTRIBUTES.get(
2094                                entry.getName(), t.getNameOrOID(), nameForm.getNameOrOID()));
2095                    }
2096                    return false;
2097                }
2098            }
2099
2100            // Make sure that all AVAs in the RDN are allowed.
2101            for (final AVA ava : rdn) {
2102                final AttributeType t = ava.getAttributeType();
2103                if (!nameForm.isRequiredOrOptional(t)) {
2104                    if (nameFormWarnings != null) {
2105                        nameFormWarnings.add(ERR_ENTRY_SCHEMA_NF_DISALLOWED_ATTRIBUTES.get(
2106                                entry.getName(), t.getNameOrOID(), nameForm.getNameOrOID()));
2107                    }
2108                    return false;
2109                }
2110            }
2111        }
2112
2113        // If we've gotten here, then things are OK.
2114        return true;
2115    }
2116
2117    private ObjectClass getParentStructuralObjectClass(final Entry entry,
2118            final SchemaValidationPolicy policy, final List<LocalizableMessage> ruleWarnings) {
2119        final Entry parentEntry;
2120        try {
2121            parentEntry =
2122                    policy.checkDITStructureRulesEntryResolver().getEntry(entry.getName().parent());
2123        } catch (final LdapException e) {
2124            if (ruleWarnings != null) {
2125                ruleWarnings.add(ERR_ENTRY_SCHEMA_DSR_PARENT_NOT_FOUND.get(
2126                        entry.getName(), e.getResult().getDiagnosticMessage()));
2127            }
2128            return null;
2129        }
2130
2131        final ObjectClass parentStructuralObjectClass =
2132                Entries.getStructuralObjectClass(parentEntry, this);
2133        if (parentStructuralObjectClass == null) {
2134            if (ruleWarnings != null) {
2135                ruleWarnings.add(ERR_ENTRY_SCHEMA_DSR_NO_PARENT_OC.get(entry.getName()));
2136            }
2137            return null;
2138        }
2139        return parentStructuralObjectClass;
2140    }
2141
2142    @Override
2143    public String toString() {
2144        return "Schema " + getSchemaName()
2145                + " mr=" + getMatchingRules().size()
2146                + " syntaxes=" + getSyntaxes().size()
2147                + " at=" + getAttributeTypes().size();
2148    }
2149}