001/*
002 * Copyright 2007-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.schema;
022
023
024
025import java.io.File;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.Serializable;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.Collections;
032import java.util.LinkedHashMap;
033import java.util.LinkedHashSet;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037import java.util.concurrent.atomic.AtomicReference;
038
039import com.unboundid.ldap.sdk.Attribute;
040import com.unboundid.ldap.sdk.Entry;
041import com.unboundid.ldap.sdk.Filter;
042import com.unboundid.ldap.sdk.LDAPConnection;
043import com.unboundid.ldap.sdk.LDAPException;
044import com.unboundid.ldap.sdk.ReadOnlyEntry;
045import com.unboundid.ldap.sdk.ResultCode;
046import com.unboundid.ldap.sdk.SearchScope;
047import com.unboundid.ldif.LDIFException;
048import com.unboundid.ldif.LDIFReader;
049import com.unboundid.util.Debug;
050import com.unboundid.util.NotMutable;
051import com.unboundid.util.StaticUtils;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.Validator;
055
056import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
057
058
059
060/**
061 * This class provides a data structure for representing a directory server
062 * subschema subentry.  This includes information about the attribute syntaxes,
063 * matching rules, attribute types, object classes, name forms, DIT content
064 * rules, DIT structure rules, and matching rule uses defined in the server
065 * schema.
066 */
067@NotMutable()
068@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
069public final class Schema
070       implements Serializable
071{
072  /**
073   * The name of the attribute used to hold the attribute syntax definitions.
074   */
075  public static final String ATTR_ATTRIBUTE_SYNTAX = "ldapSyntaxes";
076
077
078
079  /**
080   * The name of the attribute used to hold the attribute type definitions.
081   */
082  public static final String ATTR_ATTRIBUTE_TYPE = "attributeTypes";
083
084
085
086  /**
087   * The name of the attribute used to hold the DIT content rule definitions.
088   */
089  public static final String ATTR_DIT_CONTENT_RULE = "dITContentRules";
090
091
092
093  /**
094   * The name of the attribute used to hold the DIT structure rule definitions.
095   */
096  public static final String ATTR_DIT_STRUCTURE_RULE = "dITStructureRules";
097
098
099
100  /**
101   * The name of the attribute used to hold the matching rule definitions.
102   */
103  public static final String ATTR_MATCHING_RULE = "matchingRules";
104
105
106
107  /**
108   * The name of the attribute used to hold the matching rule use definitions.
109   */
110  public static final String ATTR_MATCHING_RULE_USE = "matchingRuleUse";
111
112
113
114  /**
115   * The name of the attribute used to hold the name form definitions.
116   */
117  public static final String ATTR_NAME_FORM = "nameForms";
118
119
120
121  /**
122   * The name of the attribute used to hold the object class definitions.
123   */
124  public static final String ATTR_OBJECT_CLASS = "objectClasses";
125
126
127
128  /**
129   * The name of the attribute used to hold the DN of the subschema subentry
130   * with the schema information that governs a specified entry.
131   */
132  public static final String ATTR_SUBSCHEMA_SUBENTRY = "subschemaSubentry";
133
134
135
136  /**
137   * The default standard schema available for use in the LDAP SDK.
138   */
139  private static final AtomicReference<Schema> DEFAULT_STANDARD_SCHEMA =
140       new AtomicReference<>();
141
142
143
144  /**
145   * The set of request attributes that will be used when retrieving the server
146   * subschema subentry in order to retrieve all of the schema elements.
147   */
148  private static final String[] SCHEMA_REQUEST_ATTRS =
149  {
150    "*",
151    ATTR_ATTRIBUTE_SYNTAX,
152    ATTR_ATTRIBUTE_TYPE,
153    ATTR_DIT_CONTENT_RULE,
154    ATTR_DIT_STRUCTURE_RULE,
155    ATTR_MATCHING_RULE,
156    ATTR_MATCHING_RULE_USE,
157    ATTR_NAME_FORM,
158    ATTR_OBJECT_CLASS
159  };
160
161
162
163  /**
164   * The set of request attributes that will be used when retrieving the
165   * subschema subentry attribute from a specified entry in order to determine
166   * the location of the server schema definitions.
167   */
168  private static final String[] SUBSCHEMA_SUBENTRY_REQUEST_ATTRS =
169  {
170    ATTR_SUBSCHEMA_SUBENTRY
171  };
172
173
174
175  /**
176   * Retrieves the resource path that may be used to obtain a file with a number
177   * of standard schema definitions.
178   */
179  private static final String DEFAULT_SCHEMA_RESOURCE_PATH =
180       "com/unboundid/ldap/sdk/schema/standard-schema.ldif";
181
182
183
184  /**
185   * The serial version UID for this serializable class.
186   */
187  private static final long serialVersionUID = 8081839633831517925L;
188
189
190
191  // A map of all subordinate attribute type definitions for each attribute
192  // type definition.
193  private final Map<AttributeTypeDefinition,List<AttributeTypeDefinition>>
194       subordinateAttributeTypes;
195
196  // The set of attribute syntaxes mapped from lowercase name/OID to syntax.
197  private final Map<String,AttributeSyntaxDefinition> asMap;
198
199  // The set of attribute types mapped from lowercase name/OID to type.
200  private final Map<String,AttributeTypeDefinition> atMap;
201
202  // The set of DIT content rules mapped from lowercase name/OID to rule.
203  private final Map<String,DITContentRuleDefinition> dcrMap;
204
205  // The set of DIT structure rules mapped from rule ID to rule.
206  private final Map<Integer,DITStructureRuleDefinition> dsrMapByID;
207
208  // The set of DIT structure rules mapped from lowercase name to rule.
209  private final Map<String,DITStructureRuleDefinition> dsrMapByName;
210
211  // The set of DIT structure rules mapped from lowercase name to rule.
212  private final Map<String,DITStructureRuleDefinition> dsrMapByNameForm;
213
214  // The set of matching rules mapped from lowercase name/OID to rule.
215  private final Map<String,MatchingRuleDefinition> mrMap;
216
217  // The set of matching rule uses mapped from matching rule OID to use.
218  private final Map<String,MatchingRuleUseDefinition> mruMap;
219
220  // The set of name forms mapped from lowercase name/OID to name form.
221  private final Map<String,NameFormDefinition> nfMapByName;
222
223  // The set of name forms mapped from structural class OID to name form.
224  private final Map<String,NameFormDefinition> nfMapByOC;
225
226  // The set of object classes mapped from lowercase name/OID to class.
227  private final Map<String,ObjectClassDefinition> ocMap;
228
229  // The entry used to create this schema object.
230  private final ReadOnlyEntry schemaEntry;
231
232  // The set of attribute syntaxes defined in the schema.
233  private final Set<AttributeSyntaxDefinition> asSet;
234
235  // The set of attribute types defined in the schema.
236  private final Set<AttributeTypeDefinition> atSet;
237
238  // The set of operational attribute types defined in the schema.
239  private final Set<AttributeTypeDefinition> operationalATSet;
240
241  // The set of user attribute types defined in the schema.
242  private final Set<AttributeTypeDefinition> userATSet;
243
244  // The set of DIT content rules defined in the schema.
245  private final Set<DITContentRuleDefinition> dcrSet;
246
247  // The set of DIT structure rules defined in the schema.
248  private final Set<DITStructureRuleDefinition> dsrSet;
249
250  // The set of matching rules defined in the schema.
251  private final Set<MatchingRuleDefinition> mrSet;
252
253  // The set of matching rule uses defined in the schema.
254  private final Set<MatchingRuleUseDefinition> mruSet;
255
256  // The set of name forms defined in the schema.
257  private final Set<NameFormDefinition> nfSet;
258
259  // The set of object classes defined in the schema.
260  private final Set<ObjectClassDefinition> ocSet;
261
262  // The set of abstract object classes defined in the schema.
263  private final Set<ObjectClassDefinition> abstractOCSet;
264
265  // The set of auxiliary object classes defined in the schema.
266  private final Set<ObjectClassDefinition> auxiliaryOCSet;
267
268  // The set of structural object classes defined in the schema.
269  private final Set<ObjectClassDefinition> structuralOCSet;
270
271
272
273  /**
274   * Creates a new schema object by decoding the information in the provided
275   * entry.  Any schema elements that cannot be parsed will be silently ignored.
276   *
277   * @param  schemaEntry  The schema entry to decode.  It must not be
278   *                      {@code null}.
279   */
280  public Schema(final Entry schemaEntry)
281  {
282    this(schemaEntry, null, null, null, null, null, null, null, null);
283  }
284
285
286
287  /**
288   * Creates a new schema object by decoding the information in the provided
289   * entry, optionally capturing any information about unparsable values in the
290   * provided maps.
291   *
292   * @param  schemaEntry                  The schema entry to decode.  It must
293   *                                      not be {@code null}.
294   * @param  unparsableAttributeSyntaxes  A map that will be updated with with
295   *                                      information about any attribute syntax
296   *                                      definitions that cannot be parsed.  It
297   *                                      may be {@code null} if unparsable
298   *                                      attribute syntax definitions should be
299   *                                      silently ignored.
300   * @param  unparsableMatchingRules      A map that will be updated with with
301   *                                      information about any matching rule
302   *                                      definitions that cannot be parsed.  It
303   *                                      may be {@code null} if unparsable
304   *                                      attribute syntax definitions should be
305   *                                      silently ignored.
306   * @param  unparsableAttributeTypes     A map that will be updated with with
307   *                                      information about any attribute type
308   *                                      definitions that cannot be parsed.  It
309   *                                      may be {@code null} if unparsable
310   *                                      attribute syntax definitions should be
311   *                                      silently ignored.
312   * @param  unparsableObjectClasses      A map that will be updated with with
313   *                                      information about any object class
314   *                                      definitions that cannot be parsed.  It
315   *                                      may be {@code null} if unparsable
316   *                                      attribute syntax definitions should be
317   *                                      silently ignored.
318   * @param  unparsableDITContentRules    A map that will be updated with with
319   *                                      information about any DIT content rule
320   *                                      definitions that cannot be parsed.  It
321   *                                      may be {@code null} if unparsable
322   *                                      attribute syntax definitions should be
323   *                                      silently ignored.
324   * @param  unparsableDITStructureRules  A map that will be updated with with
325   *                                      information about any DIT structure
326   *                                      rule definitions that cannot be
327   *                                      parsed.  It may be {@code null} if
328   *                                      unparsable attribute syntax
329   *                                      definitions should be silently
330   *                                      ignored.
331   * @param  unparsableNameForms          A map that will be updated with with
332   *                                      information about any name form
333   *                                      definitions that cannot be parsed.  It
334   *                                      may be {@code null} if unparsable
335   *                                      attribute syntax definitions should be
336   *                                      silently ignored.
337   * @param  unparsableMatchingRuleUses   A map that will be updated with with
338   *                                      information about any matching rule
339   *                                      use definitions that cannot be parsed.
340   *                                      It may be {@code null} if unparsable
341   *                                      attribute syntax definitions should be
342   *                                      silently ignored.
343   */
344  public Schema(final Entry schemaEntry,
345                final Map<String,LDAPException> unparsableAttributeSyntaxes,
346                final Map<String,LDAPException> unparsableMatchingRules,
347                final Map<String,LDAPException> unparsableAttributeTypes,
348                final Map<String,LDAPException> unparsableObjectClasses,
349                final Map<String,LDAPException> unparsableDITContentRules,
350                final Map<String,LDAPException> unparsableDITStructureRules,
351                final Map<String,LDAPException> unparsableNameForms,
352                final Map<String,LDAPException> unparsableMatchingRuleUses)
353  {
354    this.schemaEntry = new ReadOnlyEntry(schemaEntry);
355
356    // Decode the attribute syntaxes from the schema entry.
357    String[] defs = schemaEntry.getAttributeValues(ATTR_ATTRIBUTE_SYNTAX);
358    if (defs == null)
359    {
360      asMap = Collections.emptyMap();
361      asSet = Collections.emptySet();
362    }
363    else
364    {
365      final LinkedHashMap<String,AttributeSyntaxDefinition> m =
366           new LinkedHashMap<>(defs.length);
367      final LinkedHashSet<AttributeSyntaxDefinition> s =
368           new LinkedHashSet<>(defs.length);
369
370      for (final String def : defs)
371      {
372        try
373        {
374          final AttributeSyntaxDefinition as =
375               new AttributeSyntaxDefinition(def);
376          s.add(as);
377          m.put(StaticUtils.toLowerCase(as.getOID()), as);
378        }
379        catch (final LDAPException le)
380        {
381          Debug.debugException(le);
382          if (unparsableAttributeSyntaxes != null)
383          {
384            unparsableAttributeSyntaxes.put(def, le);
385          }
386        }
387      }
388
389      asMap = Collections.unmodifiableMap(m);
390      asSet = Collections.unmodifiableSet(s);
391    }
392
393
394    // Decode the attribute types from the schema entry.
395    defs = schemaEntry.getAttributeValues(ATTR_ATTRIBUTE_TYPE);
396    if (defs == null)
397    {
398      atMap            = Collections.emptyMap();
399      atSet            = Collections.emptySet();
400      operationalATSet = Collections.emptySet();
401      userATSet        = Collections.emptySet();
402    }
403    else
404    {
405      final LinkedHashMap<String,AttributeTypeDefinition> m =
406           new LinkedHashMap<>(2*defs.length);
407      final LinkedHashSet<AttributeTypeDefinition> s =
408           new LinkedHashSet<>(defs.length);
409      final LinkedHashSet<AttributeTypeDefinition> sUser =
410           new LinkedHashSet<>(defs.length);
411      final LinkedHashSet<AttributeTypeDefinition> sOperational =
412           new LinkedHashSet<>(defs.length);
413
414      for (final String def : defs)
415      {
416        try
417        {
418          final AttributeTypeDefinition at = new AttributeTypeDefinition(def);
419          s.add(at);
420          m.put(StaticUtils.toLowerCase(at.getOID()), at);
421          for (final String name : at.getNames())
422          {
423            m.put(StaticUtils.toLowerCase(name), at);
424          }
425
426          if (at.isOperational())
427          {
428            sOperational.add(at);
429          }
430          else
431          {
432            sUser.add(at);
433          }
434        }
435        catch (final LDAPException le)
436        {
437          Debug.debugException(le);
438          if (unparsableAttributeTypes != null)
439          {
440            unparsableAttributeTypes.put(def, le);
441          }
442        }
443      }
444
445      atMap            = Collections.unmodifiableMap(m);
446      atSet            = Collections.unmodifiableSet(s);
447      operationalATSet = Collections.unmodifiableSet(sOperational);
448      userATSet        = Collections.unmodifiableSet(sUser);
449    }
450
451
452    // Decode the DIT content rules from the schema entry.
453    defs = schemaEntry.getAttributeValues(ATTR_DIT_CONTENT_RULE);
454    if (defs == null)
455    {
456      dcrMap = Collections.emptyMap();
457      dcrSet = Collections.emptySet();
458    }
459    else
460    {
461      final LinkedHashMap<String,DITContentRuleDefinition> m =
462           new LinkedHashMap<>(2*defs.length);
463      final LinkedHashSet<DITContentRuleDefinition> s =
464           new LinkedHashSet<>(defs.length);
465
466      for (final String def : defs)
467      {
468        try
469        {
470          final DITContentRuleDefinition dcr =
471               new DITContentRuleDefinition(def);
472          s.add(dcr);
473          m.put(StaticUtils.toLowerCase(dcr.getOID()), dcr);
474          for (final String name : dcr.getNames())
475          {
476            m.put(StaticUtils.toLowerCase(name), dcr);
477          }
478        }
479        catch (final LDAPException le)
480        {
481          Debug.debugException(le);
482          if (unparsableDITContentRules != null)
483          {
484            unparsableDITContentRules.put(def, le);
485          }
486        }
487      }
488
489      dcrMap = Collections.unmodifiableMap(m);
490      dcrSet = Collections.unmodifiableSet(s);
491    }
492
493
494    // Decode the DIT structure rules from the schema entry.
495    defs = schemaEntry.getAttributeValues(ATTR_DIT_STRUCTURE_RULE);
496    if (defs == null)
497    {
498      dsrMapByID       = Collections.emptyMap();
499      dsrMapByName     = Collections.emptyMap();
500      dsrMapByNameForm = Collections.emptyMap();
501      dsrSet           = Collections.emptySet();
502    }
503    else
504    {
505      final LinkedHashMap<Integer,DITStructureRuleDefinition> mID =
506           new LinkedHashMap<>(defs.length);
507      final LinkedHashMap<String,DITStructureRuleDefinition> mN =
508           new LinkedHashMap<>(defs.length);
509      final LinkedHashMap<String,DITStructureRuleDefinition> mNF =
510           new LinkedHashMap<>(defs.length);
511      final LinkedHashSet<DITStructureRuleDefinition> s =
512           new LinkedHashSet<>(defs.length);
513
514      for (final String def : defs)
515      {
516        try
517        {
518          final DITStructureRuleDefinition dsr =
519               new DITStructureRuleDefinition(def);
520          s.add(dsr);
521          mID.put(dsr.getRuleID(), dsr);
522          mNF.put(StaticUtils.toLowerCase(dsr.getNameFormID()), dsr);
523          for (final String name : dsr.getNames())
524          {
525            mN.put(StaticUtils.toLowerCase(name), dsr);
526          }
527        }
528        catch (final LDAPException le)
529        {
530          Debug.debugException(le);
531          if (unparsableDITStructureRules != null)
532          {
533            unparsableDITStructureRules.put(def, le);
534          }
535        }
536      }
537
538      dsrMapByID       = Collections.unmodifiableMap(mID);
539      dsrMapByName     = Collections.unmodifiableMap(mN);
540      dsrMapByNameForm = Collections.unmodifiableMap(mNF);
541      dsrSet           = Collections.unmodifiableSet(s);
542    }
543
544
545    // Decode the matching rules from the schema entry.
546    defs = schemaEntry.getAttributeValues(ATTR_MATCHING_RULE);
547    if (defs == null)
548    {
549      mrMap = Collections.emptyMap();
550      mrSet = Collections.emptySet();
551    }
552    else
553    {
554      final LinkedHashMap<String,MatchingRuleDefinition> m =
555           new LinkedHashMap<>(2*defs.length);
556      final LinkedHashSet<MatchingRuleDefinition> s =
557           new LinkedHashSet<>(defs.length);
558
559      for (final String def : defs)
560      {
561        try
562        {
563          final MatchingRuleDefinition mr = new MatchingRuleDefinition(def);
564          s.add(mr);
565          m.put(StaticUtils.toLowerCase(mr.getOID()), mr);
566          for (final String name : mr.getNames())
567          {
568            m.put(StaticUtils.toLowerCase(name), mr);
569          }
570        }
571        catch (final LDAPException le)
572        {
573          Debug.debugException(le);
574          if (unparsableMatchingRules != null)
575          {
576            unparsableMatchingRules.put(def, le);
577          }
578        }
579      }
580
581      mrMap = Collections.unmodifiableMap(m);
582      mrSet = Collections.unmodifiableSet(s);
583    }
584
585
586    // Decode the matching rule uses from the schema entry.
587    defs = schemaEntry.getAttributeValues(ATTR_MATCHING_RULE_USE);
588    if (defs == null)
589    {
590      mruMap = Collections.emptyMap();
591      mruSet = Collections.emptySet();
592    }
593    else
594    {
595      final LinkedHashMap<String,MatchingRuleUseDefinition> m =
596           new LinkedHashMap<>(2*defs.length);
597      final LinkedHashSet<MatchingRuleUseDefinition> s =
598           new LinkedHashSet<>(defs.length);
599
600      for (final String def : defs)
601      {
602        try
603        {
604          final MatchingRuleUseDefinition mru =
605               new MatchingRuleUseDefinition(def);
606          s.add(mru);
607          m.put(StaticUtils.toLowerCase(mru.getOID()), mru);
608          for (final String name : mru.getNames())
609          {
610            m.put(StaticUtils.toLowerCase(name), mru);
611          }
612        }
613        catch (final LDAPException le)
614        {
615          Debug.debugException(le);
616          if (unparsableMatchingRuleUses != null)
617          {
618            unparsableMatchingRuleUses.put(def, le);
619          }
620        }
621      }
622
623      mruMap = Collections.unmodifiableMap(m);
624      mruSet = Collections.unmodifiableSet(s);
625    }
626
627
628    // Decode the name forms from the schema entry.
629    defs = schemaEntry.getAttributeValues(ATTR_NAME_FORM);
630    if (defs == null)
631    {
632      nfMapByName = Collections.emptyMap();
633      nfMapByOC   = Collections.emptyMap();
634      nfSet       = Collections.emptySet();
635    }
636    else
637    {
638      final LinkedHashMap<String,NameFormDefinition> mN =
639           new LinkedHashMap<>(2*defs.length);
640      final LinkedHashMap<String,NameFormDefinition> mOC =
641           new LinkedHashMap<>(defs.length);
642      final LinkedHashSet<NameFormDefinition> s =
643           new LinkedHashSet<>(defs.length);
644
645      for (final String def : defs)
646      {
647        try
648        {
649          final NameFormDefinition nf = new NameFormDefinition(def);
650          s.add(nf);
651          mOC.put(StaticUtils.toLowerCase(nf.getStructuralClass()), nf);
652          mN.put(StaticUtils.toLowerCase(nf.getOID()), nf);
653          for (final String name : nf.getNames())
654          {
655            mN.put(StaticUtils.toLowerCase(name), nf);
656          }
657        }
658        catch (final LDAPException le)
659        {
660          Debug.debugException(le);
661          if(unparsableNameForms != null)
662          {
663            unparsableNameForms.put(def, le);
664          }
665        }
666      }
667
668      nfMapByName = Collections.unmodifiableMap(mN);
669      nfMapByOC   = Collections.unmodifiableMap(mOC);
670      nfSet       = Collections.unmodifiableSet(s);
671    }
672
673
674    // Decode the object classes from the schema entry.
675    defs = schemaEntry.getAttributeValues(ATTR_OBJECT_CLASS);
676    if (defs == null)
677    {
678      ocMap           = Collections.emptyMap();
679      ocSet           = Collections.emptySet();
680      abstractOCSet   = Collections.emptySet();
681      auxiliaryOCSet  = Collections.emptySet();
682      structuralOCSet = Collections.emptySet();
683    }
684    else
685    {
686      final LinkedHashMap<String,ObjectClassDefinition> m =
687           new LinkedHashMap<>(2*defs.length);
688      final LinkedHashSet<ObjectClassDefinition> s =
689           new LinkedHashSet<>(defs.length);
690      final LinkedHashSet<ObjectClassDefinition> sAbstract =
691           new LinkedHashSet<>(defs.length);
692      final LinkedHashSet<ObjectClassDefinition> sAuxiliary =
693           new LinkedHashSet<>(defs.length);
694      final LinkedHashSet<ObjectClassDefinition> sStructural =
695           new LinkedHashSet<>(defs.length);
696
697      for (final String def : defs)
698      {
699        try
700        {
701          final ObjectClassDefinition oc = new ObjectClassDefinition(def);
702          s.add(oc);
703          m.put(StaticUtils.toLowerCase(oc.getOID()), oc);
704          for (final String name : oc.getNames())
705          {
706            m.put(StaticUtils.toLowerCase(name), oc);
707          }
708
709          switch (oc.getObjectClassType(null))
710          {
711            case ABSTRACT:
712              sAbstract.add(oc);
713              break;
714            case AUXILIARY:
715              sAuxiliary.add(oc);
716              break;
717            case STRUCTURAL:
718              sStructural.add(oc);
719              break;
720          }
721        }
722        catch (final LDAPException le)
723        {
724          Debug.debugException(le);
725          if (unparsableObjectClasses != null)
726          {
727            unparsableObjectClasses.put(def, le);
728          }
729        }
730      }
731
732      ocMap           = Collections.unmodifiableMap(m);
733      ocSet           = Collections.unmodifiableSet(s);
734      abstractOCSet   = Collections.unmodifiableSet(sAbstract);
735      auxiliaryOCSet  = Collections.unmodifiableSet(sAuxiliary);
736      structuralOCSet = Collections.unmodifiableSet(sStructural);
737    }
738
739
740    // Populate the map of subordinate attribute types.
741    final LinkedHashMap<AttributeTypeDefinition,List<AttributeTypeDefinition>>
742         subAttrTypes = new LinkedHashMap<>(atSet.size());
743    for (final AttributeTypeDefinition d : atSet)
744    {
745      AttributeTypeDefinition sup = d.getSuperiorType(this);
746      while (sup != null)
747      {
748        List<AttributeTypeDefinition> l = subAttrTypes.get(sup);
749        if (l == null)
750        {
751          l = new ArrayList<>(1);
752          subAttrTypes.put(sup, l);
753        }
754        l.add(d);
755
756        sup = sup.getSuperiorType(this);
757      }
758    }
759    subordinateAttributeTypes = Collections.unmodifiableMap(subAttrTypes);
760  }
761
762
763
764  /**
765   * Parses all schema elements contained in the provided entry.  This method
766   * differs from the {@link #Schema(Entry)} constructor in that this method
767   * will throw an exception if it encounters any unparsable schema elements,
768   * while the constructor will silently ignore them.  Alternately, the
769   * 'constructor that takes a bunch of maps can be used to
770   *
771   * @param  schemaEntry  The schema entry to parse.  It must not be
772   *                      {@code null}.
773   *
774   * @return  The schema entry that was parsed.
775   *
776   * @throws  LDAPException  If the provided entry contains any schema element
777   *                         definitions that cannot be parsed.
778   */
779  public static Schema parseSchemaEntry(final Entry schemaEntry)
780         throws LDAPException
781  {
782    final Map<String,LDAPException> unparsableAttributeSyntaxes =
783         new LinkedHashMap<>(10);
784    final Map<String,LDAPException> unparsableMatchingRules =
785         new LinkedHashMap<>(10);
786    final Map<String,LDAPException> unparsableAttributeTypes =
787         new LinkedHashMap<>(10);
788    final Map<String,LDAPException> unparsableObjectClasses =
789         new LinkedHashMap<>(10);
790    final Map<String,LDAPException> unparsableDITContentRules =
791         new LinkedHashMap<>(10);
792    final Map<String,LDAPException> unparsableDITStructureRules =
793         new LinkedHashMap<>(10);
794    final Map<String,LDAPException> unparsableNameForms =
795         new LinkedHashMap<>(10);
796    final Map<String,LDAPException> unparsableMatchingRuleUses =
797         new LinkedHashMap<>(10);
798
799    final Schema schema = new Schema(schemaEntry, unparsableAttributeSyntaxes,
800         unparsableMatchingRules, unparsableAttributeTypes,
801         unparsableObjectClasses, unparsableDITContentRules,
802         unparsableDITStructureRules, unparsableNameForms,
803         unparsableMatchingRuleUses);
804    if (unparsableAttributeSyntaxes.isEmpty() &&
805         unparsableMatchingRules.isEmpty() &&
806         unparsableAttributeTypes.isEmpty() &&
807         unparsableObjectClasses.isEmpty() &&
808         unparsableDITContentRules.isEmpty() &&
809         unparsableDITStructureRules.isEmpty() &&
810         unparsableNameForms.isEmpty() &&
811         unparsableMatchingRuleUses.isEmpty())
812    {
813      return schema;
814    }
815
816    final StringBuilder messageBuffer = new StringBuilder();
817    for (final Map.Entry<String,LDAPException> e :
818         unparsableAttributeSyntaxes.entrySet())
819    {
820      appendErrorMessage(messageBuffer,
821           ERR_SCHEMA_UNPARSABLE_AS.get(ATTR_ATTRIBUTE_SYNTAX, e.getKey(),
822                StaticUtils.getExceptionMessage(e.getValue())));
823    }
824
825    for (final Map.Entry<String,LDAPException> e :
826         unparsableMatchingRules.entrySet())
827    {
828      appendErrorMessage(messageBuffer,
829           ERR_SCHEMA_UNPARSABLE_MR.get(ATTR_MATCHING_RULE, e.getKey(),
830                StaticUtils.getExceptionMessage(e.getValue())));
831    }
832
833    for (final Map.Entry<String,LDAPException> e :
834         unparsableAttributeTypes.entrySet())
835    {
836      appendErrorMessage(messageBuffer,
837           ERR_SCHEMA_UNPARSABLE_AT.get(ATTR_ATTRIBUTE_TYPE, e.getKey(),
838                StaticUtils.getExceptionMessage(e.getValue())));
839    }
840
841    for (final Map.Entry<String,LDAPException> e :
842         unparsableObjectClasses.entrySet())
843    {
844      appendErrorMessage(messageBuffer,
845           ERR_SCHEMA_UNPARSABLE_OC.get(ATTR_OBJECT_CLASS, e.getKey(),
846                StaticUtils.getExceptionMessage(e.getValue())));
847    }
848
849    for (final Map.Entry<String,LDAPException> e :
850         unparsableDITContentRules.entrySet())
851    {
852      appendErrorMessage(messageBuffer,
853           ERR_SCHEMA_UNPARSABLE_DCR.get(ATTR_DIT_CONTENT_RULE, e.getKey(),
854                StaticUtils.getExceptionMessage(e.getValue())));
855    }
856
857    for (final Map.Entry<String,LDAPException> e :
858         unparsableDITStructureRules.entrySet())
859    {
860      appendErrorMessage(messageBuffer,
861           ERR_SCHEMA_UNPARSABLE_DSR.get(ATTR_DIT_STRUCTURE_RULE, e.getKey(),
862                StaticUtils.getExceptionMessage(e.getValue())));
863    }
864
865    for (final Map.Entry<String,LDAPException> e :
866         unparsableNameForms.entrySet())
867    {
868      appendErrorMessage(messageBuffer,
869           ERR_SCHEMA_UNPARSABLE_NF.get(ATTR_NAME_FORM, e.getKey(),
870                StaticUtils.getExceptionMessage(e.getValue())));
871    }
872
873    for (final Map.Entry<String,LDAPException> e :
874         unparsableMatchingRuleUses.entrySet())
875    {
876      appendErrorMessage(messageBuffer,
877           ERR_SCHEMA_UNPARSABLE_MRU.get(ATTR_MATCHING_RULE_USE, e.getKey(),
878                StaticUtils.getExceptionMessage(e.getValue())));
879    }
880
881    throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
882         messageBuffer.toString());
883  }
884
885
886
887  /**
888   * Appends the provided message to the given buffer, adding spaces and
889   * punctuation if necessary.
890   *
891   * @param  buffer   The buffer to which the message should be appended.
892   * @param  message  The message to append to the buffer.
893   */
894  private static void appendErrorMessage(final StringBuilder buffer,
895                                         final String message)
896  {
897    final int length = buffer.length();
898    if (length > 0)
899    {
900      if (buffer.charAt(length - 1) == '.')
901      {
902        buffer.append("  ");
903      }
904      else
905      {
906        buffer.append(".  ");
907      }
908    }
909
910    buffer.append(message);
911  }
912
913
914
915  /**
916   * Retrieves the directory server schema over the provided connection.  The
917   * root DSE will first be retrieved in order to get its subschemaSubentry DN,
918   * and then that entry will be retrieved from the server and its contents
919   * decoded as schema elements.  This should be sufficient for directories that
920   * only provide a single schema, but for directories with multiple schemas it
921   * may be necessary to specify the DN of an entry for which to retrieve the
922   * subschema subentry.  Any unparsable schema elements will be silently
923   * ignored.
924   *
925   * @param  connection  The connection to use in order to retrieve the server
926   *                     schema.  It must not be {@code null}.
927   *
928   * @return  A decoded representation of the server schema.
929   *
930   * @throws  LDAPException  If a problem occurs while obtaining the server
931   *                         schema.
932   */
933  public static Schema getSchema(final LDAPConnection connection)
934         throws LDAPException
935  {
936    return getSchema(connection, "");
937  }
938
939
940
941  /**
942   * Retrieves the directory server schema that governs the specified entry.
943   * In some servers, different portions of the DIT may be served by different
944   * schemas, and in such cases it will be necessary to provide the DN of the
945   * target entry in order to ensure that the appropriate schema which governs
946   * that entry is returned.  For servers that support only a single schema,
947   * any entry DN (including that of the root DSE) should be sufficient.  Any
948   * unparsable schema elements will be silently ignored.
949   *
950   * @param  connection  The connection to use in order to retrieve the server
951   *                     schema.  It must not be {@code null}.
952   * @param  entryDN     The DN of the entry for which to retrieve the governing
953   *                     schema.  It may be {@code null} or an empty string in
954   *                     order to retrieve the schema that governs the server's
955   *                     root DSE.
956   *
957   * @return  A decoded representation of the server schema, or {@code null} if
958   *          it is not available for some reason (e.g., the client does not
959   *          have permission to read the server schema).
960   *
961   * @throws  LDAPException  If a problem occurs while obtaining the server
962   *                         schema.
963   */
964  public static Schema getSchema(final LDAPConnection connection,
965                                 final String entryDN)
966         throws LDAPException
967  {
968    return getSchema(connection, entryDN, false);
969  }
970
971
972
973  /**
974   * Retrieves the directory server schema that governs the specified entry.
975   * In some servers, different portions of the DIT may be served by different
976   * schemas, and in such cases it will be necessary to provide the DN of the
977   * target entry in order to ensure that the appropriate schema which governs
978   * that entry is returned.  For servers that support only a single schema,
979   * any entry DN (including that of the root DSE) should be sufficient.  This
980   * method may optionally throw an exception if the retrieved schema contains
981   * one or more unparsable schema elements.
982   *
983   * @param  connection                The connection to use in order to
984   *                                   retrieve the server schema.  It must not
985   *                                   be {@code null}.
986   * @param  entryDN                   The DN of the entry for which to retrieve
987   *                                   the governing schema.  It may be
988   *                                   {@code null} or an empty string in order
989   *                                   to retrieve the schema that governs the
990   *                                   server's root DSE.
991   * @param  throwOnUnparsableElement  Indicates whether to throw an exception
992   *                                   if the schema entry that is retrieved has
993   *                                   one or more unparsable schema elements.
994   *
995   * @return  A decoded representation of the server schema, or {@code null} if
996   *          it is not available for some reason (e.g., the client does not
997   *          have permission to read the server schema).
998   *
999   * @throws  LDAPException  If a problem occurs while obtaining the server
1000   *                         schema, or if the schema contains one or more
1001   *                         unparsable elements and
1002   *                         {@code throwOnUnparsableElement} is {@code true}.
1003   */
1004  public static Schema getSchema(final LDAPConnection connection,
1005                                 final String entryDN,
1006                                 final boolean throwOnUnparsableElement)
1007         throws LDAPException
1008  {
1009    Validator.ensureNotNull(connection);
1010
1011    final String subschemaSubentryDN;
1012    if (entryDN == null)
1013    {
1014      subschemaSubentryDN = getSubschemaSubentryDN(connection, "");
1015    }
1016    else
1017    {
1018      subschemaSubentryDN = getSubschemaSubentryDN(connection, entryDN);
1019    }
1020
1021    if (subschemaSubentryDN == null)
1022    {
1023      return null;
1024    }
1025
1026    final Entry schemaEntry = connection.searchForEntry(subschemaSubentryDN,
1027         SearchScope.BASE,
1028         Filter.createEqualityFilter("objectClass", "subschema"),
1029         SCHEMA_REQUEST_ATTRS);
1030    if (schemaEntry == null)
1031    {
1032      return null;
1033    }
1034
1035    if (throwOnUnparsableElement)
1036    {
1037      return parseSchemaEntry(schemaEntry);
1038    }
1039    else
1040    {
1041      return new Schema(schemaEntry);
1042    }
1043  }
1044
1045
1046
1047  /**
1048   * Reads schema information from one or more files containing the schema
1049   * represented in LDIF form, with the definitions represented in the form
1050   * described in section 4.1 of RFC 4512.  Each file should contain a single
1051   * entry.  Any unparsable schema elements will be silently ignored.
1052   *
1053   * @param  schemaFiles  The paths to the LDIF files containing the schema
1054   *                      information to be read.  At least one file must be
1055   *                      specified.  If multiple files are specified, then they
1056   *                      will be processed in the order in which they have been
1057   *                      listed.
1058   *
1059   * @return  The schema read from the specified schema files, or {@code null}
1060   *          if none of the files contains any LDIF data to be read.
1061   *
1062   * @throws  IOException  If a problem occurs while attempting to read from
1063   *                       any of the specified files.
1064   *
1065   * @throws  LDIFException  If a problem occurs while attempting to parse the
1066   *                         contents of any of the schema files.
1067   */
1068  public static Schema getSchema(final String... schemaFiles)
1069         throws IOException, LDIFException
1070  {
1071    Validator.ensureNotNull(schemaFiles);
1072    Validator.ensureFalse(schemaFiles.length == 0);
1073
1074    final ArrayList<File> files = new ArrayList<>(schemaFiles.length);
1075    for (final String s : schemaFiles)
1076    {
1077      files.add(new File(s));
1078    }
1079
1080    return getSchema(files);
1081  }
1082
1083
1084
1085  /**
1086   * Reads schema information from one or more files containing the schema
1087   * represented in LDIF form, with the definitions represented in the form
1088   * described in section 4.1 of RFC 4512.  Each file should contain a single
1089   * entry.  Any unparsable schema elements will be silently ignored.
1090   *
1091   * @param  schemaFiles  The paths to the LDIF files containing the schema
1092   *                      information to be read.  At least one file must be
1093   *                      specified.  If multiple files are specified, then they
1094   *                      will be processed in the order in which they have been
1095   *                      listed.
1096   *
1097   * @return  The schema read from the specified schema files, or {@code null}
1098   *          if none of the files contains any LDIF data to be read.
1099   *
1100   * @throws  IOException  If a problem occurs while attempting to read from
1101   *                       any of the specified files.
1102   *
1103   * @throws  LDIFException  If a problem occurs while attempting to parse the
1104   *                         contents of any of the schema files.
1105   */
1106  public static Schema getSchema(final File... schemaFiles)
1107         throws IOException, LDIFException
1108  {
1109    Validator.ensureNotNull(schemaFiles);
1110    Validator.ensureFalse(schemaFiles.length == 0);
1111
1112    return getSchema(Arrays.asList(schemaFiles));
1113  }
1114
1115
1116
1117  /**
1118   * Reads schema information from one or more files containing the schema
1119   * represented in LDIF form, with the definitions represented in the form
1120   * described in section 4.1 of RFC 4512.  Each file should contain a single
1121   * entry.  Any unparsable schema elements will be silently ignored.
1122   *
1123   * @param  schemaFiles  The paths to the LDIF files containing the schema
1124   *                      information to be read.  At least one file must be
1125   *                      specified.  If multiple files are specified, then they
1126   *                      will be processed in the order in which they have been
1127   *                      listed.
1128   *
1129   * @return  The schema read from the specified schema files, or {@code null}
1130   *          if none of the files contains any LDIF data to be read.
1131   *
1132   * @throws  IOException  If a problem occurs while attempting to read from
1133   *                       any of the specified files.
1134   *
1135   * @throws  LDIFException  If a problem occurs while attempting to parse the
1136   *                         contents of any of the schema files.
1137   */
1138  public static Schema getSchema(final List<File> schemaFiles)
1139         throws IOException, LDIFException
1140  {
1141    return getSchema(schemaFiles, false);
1142  }
1143
1144
1145
1146  /**
1147   * Reads schema information from one or more files containing the schema
1148   * represented in LDIF form, with the definitions represented in the form
1149   * described in section 4.1 of RFC 4512.  Each file should contain a single
1150   * entry.
1151   *
1152   * @param  schemaFiles               The paths to the LDIF files containing
1153   *                                   the schema information to be read.  At
1154   *                                   least one file must be specified.  If
1155   *                                   multiple files are specified, then they
1156   *                                   will be processed in the order in which
1157   *                                   they have been listed.
1158   * @param  throwOnUnparsableElement  Indicates whether to throw an exception
1159   *                                   if the schema entry that is retrieved has
1160   *                                   one or more unparsable schema elements.
1161   *
1162   * @return  The schema read from the specified schema files, or {@code null}
1163   *          if none of the files contains any LDIF data to be read.
1164   *
1165   * @throws  IOException  If a problem occurs while attempting to read from
1166   *                       any of the specified files.
1167   *
1168   * @throws  LDIFException  If a problem occurs while attempting to parse the
1169   *                         contents of any of the schema files.  If
1170   *                         {@code throwOnUnparsableElement} is {@code true},
1171   *                         then this may also be thrown if any of the schema
1172   *                         files contains any unparsable schema elements.
1173   */
1174  public static Schema getSchema(final List<File> schemaFiles,
1175                                 final boolean throwOnUnparsableElement)
1176         throws IOException, LDIFException
1177  {
1178    Validator.ensureNotNull(schemaFiles);
1179    Validator.ensureFalse(schemaFiles.isEmpty());
1180
1181    Entry schemaEntry = null;
1182    for (final File f : schemaFiles)
1183    {
1184      final LDIFReader ldifReader = new LDIFReader(f);
1185
1186      try
1187      {
1188        final Entry e = ldifReader.readEntry();
1189        if (e == null)
1190        {
1191          continue;
1192        }
1193
1194        e.addAttribute("objectClass", "top", "ldapSubentry", "subschema");
1195
1196        if (schemaEntry == null)
1197        {
1198          schemaEntry = e;
1199        }
1200        else
1201        {
1202          for (final Attribute a : e.getAttributes())
1203          {
1204            schemaEntry.addAttribute(a);
1205          }
1206        }
1207      }
1208      finally
1209      {
1210        ldifReader.close();
1211      }
1212    }
1213
1214    if (schemaEntry == null)
1215    {
1216      return null;
1217    }
1218
1219    if (throwOnUnparsableElement)
1220    {
1221      try
1222      {
1223        return parseSchemaEntry(schemaEntry);
1224      }
1225      catch (final LDAPException e)
1226      {
1227        Debug.debugException(e);
1228        throw new LDIFException(e.getMessage(), 0, false, e);
1229      }
1230    }
1231    else
1232    {
1233      return new Schema(schemaEntry);
1234    }
1235  }
1236
1237
1238
1239  /**
1240   * Reads schema information from the provided input stream.  The information
1241   * should be in LDIF form, with the definitions represented in the form
1242   * described in section 4.1 of RFC 4512.  Only a single entry will be read
1243   * from the input stream, and it will be closed at the end of this method.
1244   *
1245   * @param  inputStream  The input stream from which the schema entry will be
1246   *                      read.  It must not be {@code null}, and it will be
1247   *                      closed when this method returns.
1248   *
1249   * @return  The schema read from the provided input stream, or {@code null} if
1250   *          the end of the input stream is reached without reading any data.
1251   *
1252   * @throws  IOException  If a problem is encountered while attempting to read
1253   *                       from the provided input stream.
1254   *
1255   * @throws  LDIFException  If a problem occurs while attempting to parse the
1256   *                         data read as LDIF.
1257   */
1258  public static Schema getSchema(final InputStream inputStream)
1259         throws IOException, LDIFException
1260  {
1261    Validator.ensureNotNull(inputStream);
1262
1263    final LDIFReader ldifReader = new LDIFReader(inputStream);
1264
1265    try
1266    {
1267      final Entry e = ldifReader.readEntry();
1268      if (e == null)
1269      {
1270        return null;
1271      }
1272      else
1273      {
1274        return new Schema(e);
1275      }
1276    }
1277    finally
1278    {
1279      ldifReader.close();
1280    }
1281  }
1282
1283
1284
1285  /**
1286   * Retrieves a schema object that contains definitions for a number of
1287   * standard attribute types and object classes from LDAP-related RFCs and
1288   * Internet Drafts.
1289   *
1290   * @return  A schema object that contains definitions for a number of standard
1291   *          attribute types and object classes from LDAP-related RFCs and
1292   *          Internet Drafts.
1293   *
1294   * @throws  LDAPException  If a problem occurs while attempting to obtain or
1295   *                         parse the default standard schema definitions.
1296   */
1297  public static Schema getDefaultStandardSchema()
1298         throws LDAPException
1299  {
1300    final Schema s = DEFAULT_STANDARD_SCHEMA.get();
1301    if (s != null)
1302    {
1303      return s;
1304    }
1305
1306    synchronized (DEFAULT_STANDARD_SCHEMA)
1307    {
1308      try
1309      {
1310        final ClassLoader classLoader = Schema.class.getClassLoader();
1311        final InputStream inputStream =
1312             classLoader.getResourceAsStream(DEFAULT_SCHEMA_RESOURCE_PATH);
1313        final LDIFReader ldifReader = new LDIFReader(inputStream);
1314        final Entry schemaEntry = ldifReader.readEntry();
1315        ldifReader.close();
1316
1317        final Schema schema = new Schema(schemaEntry);
1318        DEFAULT_STANDARD_SCHEMA.set(schema);
1319        return schema;
1320      }
1321      catch (final Exception e)
1322      {
1323        Debug.debugException(e);
1324        throw new LDAPException(ResultCode.LOCAL_ERROR,
1325             ERR_SCHEMA_CANNOT_LOAD_DEFAULT_DEFINITIONS.get(
1326                  StaticUtils.getExceptionMessage(e)),
1327             e);
1328      }
1329    }
1330  }
1331
1332
1333
1334  /**
1335   * Retrieves a schema containing all of the elements of each of the provided
1336   * schemas.
1337   *
1338   * @param  schemas  The schemas to be merged.  It must not be {@code null} or
1339   *                  empty.
1340   *
1341   * @return  A merged representation of the provided schemas.
1342   */
1343  public static Schema mergeSchemas(final Schema... schemas)
1344  {
1345    if ((schemas == null) || (schemas.length == 0))
1346    {
1347      return null;
1348    }
1349    else if (schemas.length == 1)
1350    {
1351      return schemas[0];
1352    }
1353
1354    final LinkedHashMap<String,String> asMap = new LinkedHashMap<>(100);
1355    final LinkedHashMap<String,String> atMap = new LinkedHashMap<>(100);
1356    final LinkedHashMap<String,String> dcrMap = new LinkedHashMap<>(10);
1357    final LinkedHashMap<Integer,String> dsrMap = new LinkedHashMap<>(10);
1358    final LinkedHashMap<String,String> mrMap = new LinkedHashMap<>(100);
1359    final LinkedHashMap<String,String> mruMap = new LinkedHashMap<>(10);
1360    final LinkedHashMap<String,String> nfMap = new LinkedHashMap<>(10);
1361    final LinkedHashMap<String,String> ocMap = new LinkedHashMap<>(100);
1362
1363    for (final Schema s : schemas)
1364    {
1365      for (final AttributeSyntaxDefinition as : s.asSet)
1366      {
1367        asMap.put(StaticUtils.toLowerCase(as.getOID()), as.toString());
1368      }
1369
1370      for (final AttributeTypeDefinition at : s.atSet)
1371      {
1372        atMap.put(StaticUtils.toLowerCase(at.getOID()), at.toString());
1373      }
1374
1375      for (final DITContentRuleDefinition dcr : s.dcrSet)
1376      {
1377        dcrMap.put(StaticUtils.toLowerCase(dcr.getOID()), dcr.toString());
1378      }
1379
1380      for (final DITStructureRuleDefinition dsr : s.dsrSet)
1381      {
1382        dsrMap.put(dsr.getRuleID(), dsr.toString());
1383      }
1384
1385      for (final MatchingRuleDefinition mr : s.mrSet)
1386      {
1387        mrMap.put(StaticUtils.toLowerCase(mr.getOID()), mr.toString());
1388      }
1389
1390      for (final MatchingRuleUseDefinition mru : s.mruSet)
1391      {
1392        mruMap.put(StaticUtils.toLowerCase(mru.getOID()), mru.toString());
1393      }
1394
1395      for (final NameFormDefinition nf : s.nfSet)
1396      {
1397        nfMap.put(StaticUtils.toLowerCase(nf.getOID()), nf.toString());
1398      }
1399
1400      for (final ObjectClassDefinition oc : s.ocSet)
1401      {
1402        ocMap.put(StaticUtils.toLowerCase(oc.getOID()), oc.toString());
1403      }
1404    }
1405
1406    final Entry e = new Entry(schemas[0].getSchemaEntry().getDN());
1407
1408    final Attribute ocAttr =
1409         schemas[0].getSchemaEntry().getObjectClassAttribute();
1410    if (ocAttr == null)
1411    {
1412      e.addAttribute("objectClass", "top", "ldapSubEntry", "subschema");
1413    }
1414    else
1415    {
1416      e.addAttribute(ocAttr);
1417    }
1418
1419    if (! asMap.isEmpty())
1420    {
1421      final String[] values = new String[asMap.size()];
1422      e.addAttribute(ATTR_ATTRIBUTE_SYNTAX, asMap.values().toArray(values));
1423    }
1424
1425    if (! mrMap.isEmpty())
1426    {
1427      final String[] values = new String[mrMap.size()];
1428      e.addAttribute(ATTR_MATCHING_RULE, mrMap.values().toArray(values));
1429    }
1430
1431    if (! atMap.isEmpty())
1432    {
1433      final String[] values = new String[atMap.size()];
1434      e.addAttribute(ATTR_ATTRIBUTE_TYPE, atMap.values().toArray(values));
1435    }
1436
1437    if (! ocMap.isEmpty())
1438    {
1439      final String[] values = new String[ocMap.size()];
1440      e.addAttribute(ATTR_OBJECT_CLASS, ocMap.values().toArray(values));
1441    }
1442
1443    if (! dcrMap.isEmpty())
1444    {
1445      final String[] values = new String[dcrMap.size()];
1446      e.addAttribute(ATTR_DIT_CONTENT_RULE, dcrMap.values().toArray(values));
1447    }
1448
1449    if (! dsrMap.isEmpty())
1450    {
1451      final String[] values = new String[dsrMap.size()];
1452      e.addAttribute(ATTR_DIT_STRUCTURE_RULE, dsrMap.values().toArray(values));
1453    }
1454
1455    if (! nfMap.isEmpty())
1456    {
1457      final String[] values = new String[nfMap.size()];
1458      e.addAttribute(ATTR_NAME_FORM, nfMap.values().toArray(values));
1459    }
1460
1461    if (! mruMap.isEmpty())
1462    {
1463      final String[] values = new String[mruMap.size()];
1464      e.addAttribute(ATTR_MATCHING_RULE_USE, mruMap.values().toArray(values));
1465    }
1466
1467    return new Schema(e);
1468  }
1469
1470
1471
1472  /**
1473   * Retrieves the entry used to create this schema object.
1474   *
1475   * @return  The entry used to create this schema object.
1476   */
1477  public ReadOnlyEntry getSchemaEntry()
1478  {
1479    return schemaEntry;
1480  }
1481
1482
1483
1484  /**
1485   * Retrieves the value of the subschemaSubentry attribute from the specified
1486   * entry using the provided connection.
1487   *
1488   * @param  connection  The connection to use in order to perform the search.
1489   *                     It must not be {@code null}.
1490   * @param  entryDN     The DN of the entry from which to retrieve the
1491   *                     subschemaSubentry attribute.  It may be {@code null} or
1492   *                     an empty string in order to retrieve the value from the
1493   *                     server's root DSE.
1494   *
1495   * @return  The value of the subschemaSubentry attribute from the specified
1496   *          entry, or {@code null} if it is not available for some reason
1497   *          (e.g., the client does not have permission to read the target
1498   *          entry or the subschemaSubentry attribute).
1499   *
1500   * @throws  LDAPException  If a problem occurs while attempting to retrieve
1501   *                         the specified entry.
1502   */
1503  public static String getSubschemaSubentryDN(final LDAPConnection connection,
1504                                              final String entryDN)
1505         throws LDAPException
1506  {
1507    Validator.ensureNotNull(connection);
1508
1509    final Entry e;
1510    if (entryDN == null)
1511    {
1512      e = connection.getEntry("", SUBSCHEMA_SUBENTRY_REQUEST_ATTRS);
1513    }
1514    else
1515    {
1516      e = connection.getEntry(entryDN, SUBSCHEMA_SUBENTRY_REQUEST_ATTRS);
1517    }
1518
1519    if (e == null)
1520    {
1521      return null;
1522    }
1523
1524    return e.getAttributeValue(ATTR_SUBSCHEMA_SUBENTRY);
1525  }
1526
1527
1528
1529  /**
1530   * Retrieves the set of attribute syntax definitions contained in the server
1531   * schema.
1532   *
1533   * @return  The set of attribute syntax definitions contained in the server
1534   *          schema.
1535   */
1536  public Set<AttributeSyntaxDefinition> getAttributeSyntaxes()
1537  {
1538    return asSet;
1539  }
1540
1541
1542
1543  /**
1544   * Retrieves the attribute syntax with the specified OID from the server
1545   * schema.
1546   *
1547   * @param  oid  The OID of the attribute syntax to retrieve.  It must not be
1548   *              {@code null}.  It may optionally include a minimum upper bound
1549   *              (as may appear when the syntax OID is included in an attribute
1550   *              type definition), but if it does then that portion will be
1551   *              ignored when retrieving the attribute syntax.
1552   *
1553   * @return  The requested attribute syntax, or {@code null} if there is no
1554   *          such syntax defined in the server schema.
1555   */
1556  public AttributeSyntaxDefinition getAttributeSyntax(final String oid)
1557  {
1558    Validator.ensureNotNull(oid);
1559
1560    final String lowerOID = StaticUtils.toLowerCase(oid);
1561    final int    curlyPos = lowerOID.indexOf('{');
1562
1563    if (curlyPos > 0)
1564    {
1565      return asMap.get(lowerOID.substring(0, curlyPos));
1566    }
1567    else
1568    {
1569      return asMap.get(lowerOID);
1570    }
1571  }
1572
1573
1574
1575  /**
1576   * Retrieves the set of attribute type definitions contained in the server
1577   * schema.
1578   *
1579   * @return  The set of attribute type definitions contained in the server
1580   *          schema.
1581   */
1582  public Set<AttributeTypeDefinition> getAttributeTypes()
1583  {
1584    return atSet;
1585  }
1586
1587
1588
1589  /**
1590   * Retrieves the set of operational attribute type definitions (i.e., those
1591   * definitions with a usage of directoryOperation, distributedOperation, or
1592   * dSAOperation) contained in the  server  schema.
1593   *
1594   * @return  The set of operational attribute type definitions contained in the
1595   *          server schema.
1596   */
1597  public Set<AttributeTypeDefinition> getOperationalAttributeTypes()
1598  {
1599    return operationalATSet;
1600  }
1601
1602
1603
1604  /**
1605   * Retrieves the set of user attribute type definitions (i.e., those
1606   * definitions with a usage of userApplications) contained in the  server
1607   * schema.
1608   *
1609   * @return  The set of user attribute type definitions contained in the server
1610   *          schema.
1611   */
1612  public Set<AttributeTypeDefinition> getUserAttributeTypes()
1613  {
1614    return userATSet;
1615  }
1616
1617
1618
1619  /**
1620   * Retrieves the attribute type with the specified name or OID from the server
1621   * schema.
1622   *
1623   * @param  name  The name or OID of the attribute type to retrieve.  It must
1624   *               not be {@code null}.
1625   *
1626   * @return  The requested attribute type, or {@code null} if there is no
1627   *          such attribute type defined in the server schema.
1628   */
1629  public AttributeTypeDefinition getAttributeType(final String name)
1630  {
1631    Validator.ensureNotNull(name);
1632
1633    return atMap.get(StaticUtils.toLowerCase(name));
1634  }
1635
1636
1637
1638  /**
1639   * Retrieves a list of all subordinate attribute type definitions for the
1640   * provided attribute type definition.
1641   *
1642   * @param  d  The attribute type definition for which to retrieve all
1643   *            subordinate attribute types.  It must not be {@code null}.
1644   *
1645   * @return  A list of all subordinate attribute type definitions for the
1646   *          provided attribute type definition, or an empty list if it does
1647   *          not have any subordinate types or the provided attribute type is
1648   *          not defined in the schema.
1649   */
1650  public List<AttributeTypeDefinition> getSubordinateAttributeTypes(
1651                                            final AttributeTypeDefinition d)
1652  {
1653    Validator.ensureNotNull(d);
1654
1655    final List<AttributeTypeDefinition> l = subordinateAttributeTypes.get(d);
1656    if (l == null)
1657    {
1658      return Collections.emptyList();
1659    }
1660    else
1661    {
1662      return Collections.unmodifiableList(l);
1663    }
1664  }
1665
1666
1667
1668  /**
1669   * Retrieves the set of DIT content rule definitions contained in the server
1670   * schema.
1671   *
1672   * @return  The set of DIT content rule definitions contained in the server
1673   *          schema.
1674   */
1675  public Set<DITContentRuleDefinition> getDITContentRules()
1676  {
1677    return dcrSet;
1678  }
1679
1680
1681
1682  /**
1683   * Retrieves the DIT content rule with the specified name or OID from the
1684   * server schema.
1685   *
1686   * @param  name  The name or OID of the DIT content rule to retrieve.  It must
1687   *               not be {@code null}.
1688   *
1689   * @return  The requested DIT content rule, or {@code null} if there is no
1690   *          such rule defined in the server schema.
1691   */
1692  public DITContentRuleDefinition getDITContentRule(final String name)
1693  {
1694    Validator.ensureNotNull(name);
1695
1696    return dcrMap.get(StaticUtils.toLowerCase(name));
1697  }
1698
1699
1700
1701  /**
1702   * Retrieves the set of DIT structure rule definitions contained in the server
1703   * schema.
1704   *
1705   * @return  The set of DIT structure rule definitions contained in the server
1706   *          schema.
1707   */
1708  public Set<DITStructureRuleDefinition> getDITStructureRules()
1709  {
1710    return dsrSet;
1711  }
1712
1713
1714
1715  /**
1716   * Retrieves the DIT content rule with the specified rule ID from the server
1717   * schema.
1718   *
1719   * @param  ruleID  The rule ID for the DIT structure rule to retrieve.
1720   *
1721   * @return  The requested DIT structure rule, or {@code null} if there is no
1722   *          such rule defined in the server schema.
1723   */
1724  public DITStructureRuleDefinition getDITStructureRuleByID(final int ruleID)
1725  {
1726    return dsrMapByID.get(ruleID);
1727  }
1728
1729
1730
1731  /**
1732   * Retrieves the DIT content rule with the specified name from the server
1733   * schema.
1734   *
1735   * @param  ruleName  The name of the DIT structure rule to retrieve.  It must
1736   *                   not be {@code null}.
1737   *
1738   * @return  The requested DIT structure rule, or {@code null} if there is no
1739   *          such rule defined in the server schema.
1740   */
1741  public DITStructureRuleDefinition getDITStructureRuleByName(
1742                                         final String ruleName)
1743  {
1744    Validator.ensureNotNull(ruleName);
1745
1746    return dsrMapByName.get(StaticUtils.toLowerCase(ruleName));
1747  }
1748
1749
1750
1751  /**
1752   * Retrieves the DIT content rule associated with the specified name form from
1753   * the server schema.
1754   *
1755   * @param  nameForm  The name or OID of the name form for which to retrieve
1756   *                   the associated DIT structure rule.
1757   *
1758   * @return  The requested DIT structure rule, or {@code null} if there is no
1759   *          such rule defined in the server schema.
1760   */
1761  public DITStructureRuleDefinition getDITStructureRuleByNameForm(
1762                                         final String nameForm)
1763  {
1764    Validator.ensureNotNull(nameForm);
1765
1766    return dsrMapByNameForm.get(StaticUtils.toLowerCase(nameForm));
1767  }
1768
1769
1770
1771  /**
1772   * Retrieves the set of matching rule definitions contained in the server
1773   * schema.
1774   *
1775   * @return  The set of matching rule definitions contained in the server
1776   *          schema.
1777   */
1778  public Set<MatchingRuleDefinition> getMatchingRules()
1779  {
1780    return mrSet;
1781  }
1782
1783
1784
1785  /**
1786   * Retrieves the matching rule with the specified name or OID from the server
1787   * schema.
1788   *
1789   * @param  name  The name or OID of the matching rule to retrieve.  It must
1790   *               not be {@code null}.
1791   *
1792   * @return  The requested matching rule, or {@code null} if there is no
1793   *          such rule defined in the server schema.
1794   */
1795  public MatchingRuleDefinition getMatchingRule(final String name)
1796  {
1797    Validator.ensureNotNull(name);
1798
1799    return mrMap.get(StaticUtils.toLowerCase(name));
1800  }
1801
1802
1803
1804  /**
1805   * Retrieves the set of matching rule use definitions contained in the server
1806   * schema.
1807   *
1808   * @return  The set of matching rule use definitions contained in the server
1809   *          schema.
1810   */
1811  public Set<MatchingRuleUseDefinition> getMatchingRuleUses()
1812  {
1813    return mruSet;
1814  }
1815
1816
1817
1818  /**
1819   * Retrieves the matching rule use with the specified name or OID from the
1820   * server schema.
1821   *
1822   * @param  name  The name or OID of the matching rule use to retrieve.  It
1823   *               must not be {@code null}.
1824   *
1825   * @return  The requested matching rule, or {@code null} if there is no
1826   *          such matching rule use defined in the server schema.
1827   */
1828  public MatchingRuleUseDefinition getMatchingRuleUse(final String name)
1829  {
1830    Validator.ensureNotNull(name);
1831
1832    return mruMap.get(StaticUtils.toLowerCase(name));
1833  }
1834
1835
1836
1837  /**
1838   * Retrieves the set of name form definitions contained in the server schema.
1839   *
1840   * @return  The set of name form definitions contained in the server schema.
1841   */
1842  public Set<NameFormDefinition> getNameForms()
1843  {
1844    return nfSet;
1845  }
1846
1847
1848
1849  /**
1850   * Retrieves the name form with the specified name or OID from the server
1851   * schema.
1852   *
1853   * @param  name  The name or OID of the name form to retrieve.  It must not be
1854   *               {@code null}.
1855   *
1856   * @return  The requested name form, or {@code null} if there is no
1857   *          such rule defined in the server schema.
1858   */
1859  public NameFormDefinition getNameFormByName(final String name)
1860  {
1861    Validator.ensureNotNull(name);
1862
1863    return nfMapByName.get(StaticUtils.toLowerCase(name));
1864  }
1865
1866
1867
1868  /**
1869   * Retrieves the name form associated with the specified structural object
1870   * class from the server schema.
1871   *
1872   * @param  objectClass  The name or OID of the structural object class for
1873   *                      which to retrieve the associated name form.  It must
1874   *                      not be {@code null}.
1875   *
1876   * @return  The requested name form, or {@code null} if there is no
1877   *          such rule defined in the server schema.
1878   */
1879  public NameFormDefinition getNameFormByObjectClass(final String objectClass)
1880  {
1881    Validator.ensureNotNull(objectClass);
1882
1883    return nfMapByOC.get(StaticUtils.toLowerCase(objectClass));
1884  }
1885
1886
1887
1888  /**
1889   * Retrieves the set of object class definitions contained in the server
1890   * schema.
1891   *
1892   * @return  The set of object class definitions contained in the server
1893   *          schema.
1894   */
1895  public Set<ObjectClassDefinition> getObjectClasses()
1896  {
1897    return ocSet;
1898  }
1899
1900
1901
1902  /**
1903   * Retrieves the set of abstract object class definitions contained in the
1904   * server schema.
1905   *
1906   * @return  The set of abstract object class definitions contained in the
1907   *          server schema.
1908   */
1909  public Set<ObjectClassDefinition> getAbstractObjectClasses()
1910  {
1911    return abstractOCSet;
1912  }
1913
1914
1915
1916  /**
1917   * Retrieves the set of auxiliary object class definitions contained in the
1918   * server schema.
1919   *
1920   * @return  The set of auxiliary object class definitions contained in the
1921   *          server schema.
1922   */
1923  public Set<ObjectClassDefinition> getAuxiliaryObjectClasses()
1924  {
1925    return auxiliaryOCSet;
1926  }
1927
1928
1929
1930  /**
1931   * Retrieves the set of structural object class definitions contained in the
1932   * server schema.
1933   *
1934   * @return  The set of structural object class definitions contained in the
1935   *          server schema.
1936   */
1937  public Set<ObjectClassDefinition> getStructuralObjectClasses()
1938  {
1939    return structuralOCSet;
1940  }
1941
1942
1943
1944  /**
1945   * Retrieves the object class with the specified name or OID from the server
1946   * schema.
1947   *
1948   * @param  name  The name or OID of the object class to retrieve.  It must
1949   *               not be {@code null}.
1950   *
1951   * @return  The requested object class, or {@code null} if there is no such
1952   *          class defined in the server schema.
1953   */
1954  public ObjectClassDefinition getObjectClass(final String name)
1955  {
1956    Validator.ensureNotNull(name);
1957
1958    return ocMap.get(StaticUtils.toLowerCase(name));
1959  }
1960
1961
1962
1963  /**
1964   * Retrieves a hash code for this schema object.
1965   *
1966   * @return  A hash code for this schema object.
1967   */
1968  @Override()
1969  public int hashCode()
1970  {
1971    int hc;
1972    try
1973    {
1974      hc = schemaEntry.getParsedDN().hashCode();
1975    }
1976    catch (final Exception e)
1977    {
1978      Debug.debugException(e);
1979      hc = StaticUtils.toLowerCase(schemaEntry.getDN()).hashCode();
1980    }
1981
1982    Attribute a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_SYNTAX);
1983    if (a != null)
1984    {
1985      hc += a.hashCode();
1986    }
1987
1988    a = schemaEntry.getAttribute(ATTR_MATCHING_RULE);
1989    if (a != null)
1990    {
1991      hc += a.hashCode();
1992    }
1993
1994    a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_TYPE);
1995    if (a != null)
1996    {
1997      hc += a.hashCode();
1998    }
1999
2000    a = schemaEntry.getAttribute(ATTR_OBJECT_CLASS);
2001    if (a != null)
2002    {
2003      hc += a.hashCode();
2004    }
2005
2006    a = schemaEntry.getAttribute(ATTR_NAME_FORM);
2007    if (a != null)
2008    {
2009      hc += a.hashCode();
2010    }
2011
2012    a = schemaEntry.getAttribute(ATTR_DIT_CONTENT_RULE);
2013    if (a != null)
2014    {
2015      hc += a.hashCode();
2016    }
2017
2018    a = schemaEntry.getAttribute(ATTR_DIT_STRUCTURE_RULE);
2019    if (a != null)
2020    {
2021      hc += a.hashCode();
2022    }
2023
2024    a = schemaEntry.getAttribute(ATTR_MATCHING_RULE_USE);
2025    if (a != null)
2026    {
2027      hc += a.hashCode();
2028    }
2029
2030    return hc;
2031  }
2032
2033
2034
2035  /**
2036   * Indicates whether the provided object is equal to this schema object.
2037   *
2038   * @param  o  The object for which to make the determination.
2039   *
2040   * @return  {@code true} if the provided object is equal to this schema
2041   *          object, or {@code false} if not.
2042   */
2043  @Override()
2044  public boolean equals(final Object o)
2045  {
2046    if (o == null)
2047    {
2048      return false;
2049    }
2050
2051    if (o == this)
2052    {
2053      return true;
2054    }
2055
2056    if (! (o instanceof Schema))
2057    {
2058      return false;
2059    }
2060
2061    final Schema s = (Schema) o;
2062
2063    try
2064    {
2065      if (! schemaEntry.getParsedDN().equals(s.schemaEntry.getParsedDN()))
2066      {
2067        return false;
2068      }
2069    }
2070    catch (final Exception e)
2071    {
2072      Debug.debugException(e);
2073      if (! schemaEntry.getDN().equalsIgnoreCase(s.schemaEntry.getDN()))
2074      {
2075        return false;
2076      }
2077    }
2078
2079    return (asSet.equals(s.asSet) &&
2080         mrSet.equals(s.mrSet) &&
2081         atSet.equals(s.atSet) &&
2082         ocSet.equals(s.ocSet) &&
2083         nfSet.equals(s.nfSet) &&
2084         dcrSet.equals(s.dcrSet) &&
2085         dsrSet.equals(s.dsrSet) &&
2086         mruSet.equals(s.mruSet));
2087  }
2088
2089
2090
2091  /**
2092   * Retrieves a string representation of the associated schema entry.
2093   *
2094   * @return  A string representation of the associated schema entry.
2095   */
2096  @Override()
2097  public String toString()
2098  {
2099    return schemaEntry.toString();
2100  }
2101}