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