001/*
002 * Copyright 2007-2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2019 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<>(StaticUtils.computeMapCapacity(defs.length));
367      final LinkedHashSet<AttributeSyntaxDefinition> s =
368           new LinkedHashSet<>(StaticUtils.computeMapCapacity(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<>(StaticUtils.computeMapCapacity(2*defs.length));
407      final LinkedHashSet<AttributeTypeDefinition> s =
408           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
409      final LinkedHashSet<AttributeTypeDefinition> sUser =
410           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
411      final LinkedHashSet<AttributeTypeDefinition> sOperational =
412           new LinkedHashSet<>(StaticUtils.computeMapCapacity(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<>(StaticUtils.computeMapCapacity(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<>(StaticUtils.computeMapCapacity(defs.length));
507      final LinkedHashMap<String,DITStructureRuleDefinition> mN =
508           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
509      final LinkedHashMap<String,DITStructureRuleDefinition> mNF =
510           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
511      final LinkedHashSet<DITStructureRuleDefinition> s =
512           new LinkedHashSet<>(StaticUtils.computeMapCapacity(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<>(StaticUtils.computeMapCapacity(2*defs.length));
556      final LinkedHashSet<MatchingRuleDefinition> s =
557           new LinkedHashSet<>(StaticUtils.computeMapCapacity(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<>(StaticUtils.computeMapCapacity(2*defs.length));
597      final LinkedHashSet<MatchingRuleUseDefinition> s =
598           new LinkedHashSet<>(StaticUtils.computeMapCapacity(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<>(StaticUtils.computeMapCapacity(2*defs.length));
640      final LinkedHashMap<String,NameFormDefinition> mOC =
641           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
642      final LinkedHashSet<NameFormDefinition> s =
643           new LinkedHashSet<>(StaticUtils.computeMapCapacity(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<>(StaticUtils.computeMapCapacity(2*defs.length));
688      final LinkedHashSet<ObjectClassDefinition> s =
689           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
690      final LinkedHashSet<ObjectClassDefinition> sAbstract =
691           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
692      final LinkedHashSet<ObjectClassDefinition> sAuxiliary =
693           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
694      final LinkedHashSet<ObjectClassDefinition> sStructural =
695           new LinkedHashSet<>(StaticUtils.computeMapCapacity(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<>(
743              StaticUtils.computeMapCapacity(atSet.size()));
744    for (final AttributeTypeDefinition d : atSet)
745    {
746      AttributeTypeDefinition sup = d.getSuperiorType(this);
747      while (sup != null)
748      {
749        List<AttributeTypeDefinition> l = subAttrTypes.get(sup);
750        if (l == null)
751        {
752          l = new ArrayList<>(1);
753          subAttrTypes.put(sup, l);
754        }
755        l.add(d);
756
757        sup = sup.getSuperiorType(this);
758      }
759    }
760    subordinateAttributeTypes = Collections.unmodifiableMap(subAttrTypes);
761  }
762
763
764
765  /**
766   * Parses all schema elements contained in the provided entry.  This method
767   * differs from the {@link #Schema(Entry)} constructor in that this method
768   * will throw an exception if it encounters any unparsable schema elements,
769   * while the constructor will silently ignore them.  Alternately, the
770   * 'constructor that takes a bunch of maps can be used to
771   *
772   * @param  schemaEntry  The schema entry to parse.  It must not be
773   *                      {@code null}.
774   *
775   * @return  The schema entry that was parsed.
776   *
777   * @throws  LDAPException  If the provided entry contains any schema element
778   *                         definitions that cannot be parsed.
779   */
780  public static Schema parseSchemaEntry(final Entry schemaEntry)
781         throws LDAPException
782  {
783    final Map<String,LDAPException> unparsableAttributeSyntaxes =
784         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
785    final Map<String,LDAPException> unparsableMatchingRules =
786         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
787    final Map<String,LDAPException> unparsableAttributeTypes =
788         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
789    final Map<String,LDAPException> unparsableObjectClasses =
790         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
791    final Map<String,LDAPException> unparsableDITContentRules =
792         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
793    final Map<String,LDAPException> unparsableDITStructureRules =
794         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
795    final Map<String,LDAPException> unparsableNameForms =
796         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
797    final Map<String,LDAPException> unparsableMatchingRuleUses =
798         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
799
800    final Schema schema = new Schema(schemaEntry, unparsableAttributeSyntaxes,
801         unparsableMatchingRules, unparsableAttributeTypes,
802         unparsableObjectClasses, unparsableDITContentRules,
803         unparsableDITStructureRules, unparsableNameForms,
804         unparsableMatchingRuleUses);
805    if (unparsableAttributeSyntaxes.isEmpty() &&
806         unparsableMatchingRules.isEmpty() &&
807         unparsableAttributeTypes.isEmpty() &&
808         unparsableObjectClasses.isEmpty() &&
809         unparsableDITContentRules.isEmpty() &&
810         unparsableDITStructureRules.isEmpty() &&
811         unparsableNameForms.isEmpty() &&
812         unparsableMatchingRuleUses.isEmpty())
813    {
814      return schema;
815    }
816
817    final StringBuilder messageBuffer = new StringBuilder();
818    for (final Map.Entry<String,LDAPException> e :
819         unparsableAttributeSyntaxes.entrySet())
820    {
821      appendErrorMessage(messageBuffer,
822           ERR_SCHEMA_UNPARSABLE_AS.get(ATTR_ATTRIBUTE_SYNTAX, e.getKey(),
823                StaticUtils.getExceptionMessage(e.getValue())));
824    }
825
826    for (final Map.Entry<String,LDAPException> e :
827         unparsableMatchingRules.entrySet())
828    {
829      appendErrorMessage(messageBuffer,
830           ERR_SCHEMA_UNPARSABLE_MR.get(ATTR_MATCHING_RULE, e.getKey(),
831                StaticUtils.getExceptionMessage(e.getValue())));
832    }
833
834    for (final Map.Entry<String,LDAPException> e :
835         unparsableAttributeTypes.entrySet())
836    {
837      appendErrorMessage(messageBuffer,
838           ERR_SCHEMA_UNPARSABLE_AT.get(ATTR_ATTRIBUTE_TYPE, e.getKey(),
839                StaticUtils.getExceptionMessage(e.getValue())));
840    }
841
842    for (final Map.Entry<String,LDAPException> e :
843         unparsableObjectClasses.entrySet())
844    {
845      appendErrorMessage(messageBuffer,
846           ERR_SCHEMA_UNPARSABLE_OC.get(ATTR_OBJECT_CLASS, e.getKey(),
847                StaticUtils.getExceptionMessage(e.getValue())));
848    }
849
850    for (final Map.Entry<String,LDAPException> e :
851         unparsableDITContentRules.entrySet())
852    {
853      appendErrorMessage(messageBuffer,
854           ERR_SCHEMA_UNPARSABLE_DCR.get(ATTR_DIT_CONTENT_RULE, e.getKey(),
855                StaticUtils.getExceptionMessage(e.getValue())));
856    }
857
858    for (final Map.Entry<String,LDAPException> e :
859         unparsableDITStructureRules.entrySet())
860    {
861      appendErrorMessage(messageBuffer,
862           ERR_SCHEMA_UNPARSABLE_DSR.get(ATTR_DIT_STRUCTURE_RULE, e.getKey(),
863                StaticUtils.getExceptionMessage(e.getValue())));
864    }
865
866    for (final Map.Entry<String,LDAPException> e :
867         unparsableNameForms.entrySet())
868    {
869      appendErrorMessage(messageBuffer,
870           ERR_SCHEMA_UNPARSABLE_NF.get(ATTR_NAME_FORM, e.getKey(),
871                StaticUtils.getExceptionMessage(e.getValue())));
872    }
873
874    for (final Map.Entry<String,LDAPException> e :
875         unparsableMatchingRuleUses.entrySet())
876    {
877      appendErrorMessage(messageBuffer,
878           ERR_SCHEMA_UNPARSABLE_MRU.get(ATTR_MATCHING_RULE_USE, e.getKey(),
879                StaticUtils.getExceptionMessage(e.getValue())));
880    }
881
882    throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
883         messageBuffer.toString());
884  }
885
886
887
888  /**
889   * Appends the provided message to the given buffer, adding spaces and
890   * punctuation if necessary.
891   *
892   * @param  buffer   The buffer to which the message should be appended.
893   * @param  message  The message to append to the buffer.
894   */
895  private static void appendErrorMessage(final StringBuilder buffer,
896                                         final String message)
897  {
898    final int length = buffer.length();
899    if (length > 0)
900    {
901      if (buffer.charAt(length - 1) == '.')
902      {
903        buffer.append("  ");
904      }
905      else
906      {
907        buffer.append(".  ");
908      }
909    }
910
911    buffer.append(message);
912  }
913
914
915
916  /**
917   * Retrieves the directory server schema over the provided connection.  The
918   * root DSE will first be retrieved in order to get its subschemaSubentry DN,
919   * and then that entry will be retrieved from the server and its contents
920   * decoded as schema elements.  This should be sufficient for directories that
921   * only provide a single schema, but for directories with multiple schemas it
922   * may be necessary to specify the DN of an entry for which to retrieve the
923   * subschema subentry.  Any unparsable schema elements will be silently
924   * ignored.
925   *
926   * @param  connection  The connection to use in order to retrieve the server
927   *                     schema.  It must not be {@code null}.
928   *
929   * @return  A decoded representation of the server schema.
930   *
931   * @throws  LDAPException  If a problem occurs while obtaining the server
932   *                         schema.
933   */
934  public static Schema getSchema(final LDAPConnection connection)
935         throws LDAPException
936  {
937    return getSchema(connection, "");
938  }
939
940
941
942  /**
943   * Retrieves the directory server schema that governs the specified entry.
944   * In some servers, different portions of the DIT may be served by different
945   * schemas, and in such cases it will be necessary to provide the DN of the
946   * target entry in order to ensure that the appropriate schema which governs
947   * that entry is returned.  For servers that support only a single schema,
948   * any entry DN (including that of the root DSE) should be sufficient.  Any
949   * unparsable schema elements will be silently ignored.
950   *
951   * @param  connection  The connection to use in order to retrieve the server
952   *                     schema.  It must not be {@code null}.
953   * @param  entryDN     The DN of the entry for which to retrieve the governing
954   *                     schema.  It may be {@code null} or an empty string in
955   *                     order to retrieve the schema that governs the server's
956   *                     root DSE.
957   *
958   * @return  A decoded representation of the server schema, or {@code null} if
959   *          it is not available for some reason (e.g., the client does not
960   *          have permission to read the server schema).
961   *
962   * @throws  LDAPException  If a problem occurs while obtaining the server
963   *                         schema.
964   */
965  public static Schema getSchema(final LDAPConnection connection,
966                                 final String entryDN)
967         throws LDAPException
968  {
969    return getSchema(connection, entryDN, false);
970  }
971
972
973
974  /**
975   * Retrieves the directory server schema that governs the specified entry.
976   * In some servers, different portions of the DIT may be served by different
977   * schemas, and in such cases it will be necessary to provide the DN of the
978   * target entry in order to ensure that the appropriate schema which governs
979   * that entry is returned.  For servers that support only a single schema,
980   * any entry DN (including that of the root DSE) should be sufficient.  This
981   * method may optionally throw an exception if the retrieved schema contains
982   * one or more unparsable schema elements.
983   *
984   * @param  connection                The connection to use in order to
985   *                                   retrieve the server schema.  It must not
986   *                                   be {@code null}.
987   * @param  entryDN                   The DN of the entry for which to retrieve
988   *                                   the governing schema.  It may be
989   *                                   {@code null} or an empty string in order
990   *                                   to retrieve the schema that governs the
991   *                                   server's root DSE.
992   * @param  throwOnUnparsableElement  Indicates whether to throw an exception
993   *                                   if the schema entry that is retrieved has
994   *                                   one or more unparsable schema elements.
995   *
996   * @return  A decoded representation of the server schema, or {@code null} if
997   *          it is not available for some reason (e.g., the client does not
998   *          have permission to read the server schema).
999   *
1000   * @throws  LDAPException  If a problem occurs while obtaining the server
1001   *                         schema, or if the schema contains one or more
1002   *                         unparsable elements and
1003   *                         {@code throwOnUnparsableElement} is {@code true}.
1004   */
1005  public static Schema getSchema(final LDAPConnection connection,
1006                                 final String entryDN,
1007                                 final boolean throwOnUnparsableElement)
1008         throws LDAPException
1009  {
1010    Validator.ensureNotNull(connection);
1011
1012    final String subschemaSubentryDN;
1013    if (entryDN == null)
1014    {
1015      subschemaSubentryDN = getSubschemaSubentryDN(connection, "");
1016    }
1017    else
1018    {
1019      subschemaSubentryDN = getSubschemaSubentryDN(connection, entryDN);
1020    }
1021
1022    if (subschemaSubentryDN == null)
1023    {
1024      return null;
1025    }
1026
1027    final Entry schemaEntry = connection.searchForEntry(subschemaSubentryDN,
1028         SearchScope.BASE,
1029         Filter.createEqualityFilter("objectClass", "subschema"),
1030         SCHEMA_REQUEST_ATTRS);
1031    if (schemaEntry == null)
1032    {
1033      return null;
1034    }
1035
1036    if (throwOnUnparsableElement)
1037    {
1038      return parseSchemaEntry(schemaEntry);
1039    }
1040    else
1041    {
1042      return new Schema(schemaEntry);
1043    }
1044  }
1045
1046
1047
1048  /**
1049   * Reads schema information from one or more files containing the schema
1050   * represented in LDIF form, with the definitions represented in the form
1051   * described in section 4.1 of RFC 4512.  Each file should contain a single
1052   * entry.  Any unparsable schema elements will be silently ignored.
1053   *
1054   * @param  schemaFiles  The paths to the LDIF files containing the schema
1055   *                      information to be read.  At least one file must be
1056   *                      specified.  If multiple files are specified, then they
1057   *                      will be processed in the order in which they have been
1058   *                      listed.
1059   *
1060   * @return  The schema read from the specified schema files, or {@code null}
1061   *          if none of the files contains any LDIF data to be read.
1062   *
1063   * @throws  IOException  If a problem occurs while attempting to read from
1064   *                       any of the specified files.
1065   *
1066   * @throws  LDIFException  If a problem occurs while attempting to parse the
1067   *                         contents of any of the schema files.
1068   */
1069  public static Schema getSchema(final String... schemaFiles)
1070         throws IOException, LDIFException
1071  {
1072    Validator.ensureNotNull(schemaFiles);
1073    Validator.ensureFalse(schemaFiles.length == 0);
1074
1075    final ArrayList<File> files = new ArrayList<>(schemaFiles.length);
1076    for (final String s : schemaFiles)
1077    {
1078      files.add(new File(s));
1079    }
1080
1081    return getSchema(files);
1082  }
1083
1084
1085
1086  /**
1087   * Reads schema information from one or more files containing the schema
1088   * represented in LDIF form, with the definitions represented in the form
1089   * described in section 4.1 of RFC 4512.  Each file should contain a single
1090   * entry.  Any unparsable schema elements will be silently ignored.
1091   *
1092   * @param  schemaFiles  The paths to the LDIF files containing the schema
1093   *                      information to be read.  At least one file must be
1094   *                      specified.  If multiple files are specified, then they
1095   *                      will be processed in the order in which they have been
1096   *                      listed.
1097   *
1098   * @return  The schema read from the specified schema files, or {@code null}
1099   *          if none of the files contains any LDIF data to be read.
1100   *
1101   * @throws  IOException  If a problem occurs while attempting to read from
1102   *                       any of the specified files.
1103   *
1104   * @throws  LDIFException  If a problem occurs while attempting to parse the
1105   *                         contents of any of the schema files.
1106   */
1107  public static Schema getSchema(final File... schemaFiles)
1108         throws IOException, LDIFException
1109  {
1110    Validator.ensureNotNull(schemaFiles);
1111    Validator.ensureFalse(schemaFiles.length == 0);
1112
1113    return getSchema(Arrays.asList(schemaFiles));
1114  }
1115
1116
1117
1118  /**
1119   * Reads schema information from one or more files containing the schema
1120   * represented in LDIF form, with the definitions represented in the form
1121   * described in section 4.1 of RFC 4512.  Each file should contain a single
1122   * entry.  Any unparsable schema elements will be silently ignored.
1123   *
1124   * @param  schemaFiles  The paths to the LDIF files containing the schema
1125   *                      information to be read.  At least one file must be
1126   *                      specified.  If multiple files are specified, then they
1127   *                      will be processed in the order in which they have been
1128   *                      listed.
1129   *
1130   * @return  The schema read from the specified schema files, or {@code null}
1131   *          if none of the files contains any LDIF data to be read.
1132   *
1133   * @throws  IOException  If a problem occurs while attempting to read from
1134   *                       any of the specified files.
1135   *
1136   * @throws  LDIFException  If a problem occurs while attempting to parse the
1137   *                         contents of any of the schema files.
1138   */
1139  public static Schema getSchema(final List<File> schemaFiles)
1140         throws IOException, LDIFException
1141  {
1142    return getSchema(schemaFiles, false);
1143  }
1144
1145
1146
1147  /**
1148   * Reads schema information from one or more files containing the schema
1149   * represented in LDIF form, with the definitions represented in the form
1150   * described in section 4.1 of RFC 4512.  Each file should contain a single
1151   * entry.
1152   *
1153   * @param  schemaFiles               The paths to the LDIF files containing
1154   *                                   the schema information to be read.  At
1155   *                                   least one file must be specified.  If
1156   *                                   multiple files are specified, then they
1157   *                                   will be processed in the order in which
1158   *                                   they have been listed.
1159   * @param  throwOnUnparsableElement  Indicates whether to throw an exception
1160   *                                   if the schema entry that is retrieved has
1161   *                                   one or more unparsable schema elements.
1162   *
1163   * @return  The schema read from the specified schema files, or {@code null}
1164   *          if none of the files contains any LDIF data to be read.
1165   *
1166   * @throws  IOException  If a problem occurs while attempting to read from
1167   *                       any of the specified files.
1168   *
1169   * @throws  LDIFException  If a problem occurs while attempting to parse the
1170   *                         contents of any of the schema files.  If
1171   *                         {@code throwOnUnparsableElement} is {@code true},
1172   *                         then this may also be thrown if any of the schema
1173   *                         files contains any unparsable schema elements.
1174   */
1175  public static Schema getSchema(final List<File> schemaFiles,
1176                                 final boolean throwOnUnparsableElement)
1177         throws IOException, LDIFException
1178  {
1179    Validator.ensureNotNull(schemaFiles);
1180    Validator.ensureFalse(schemaFiles.isEmpty());
1181
1182    Entry schemaEntry = null;
1183    for (final File f : schemaFiles)
1184    {
1185      final LDIFReader ldifReader = new LDIFReader(f);
1186
1187      try
1188      {
1189        final Entry e = ldifReader.readEntry();
1190        if (e == null)
1191        {
1192          continue;
1193        }
1194
1195        e.addAttribute("objectClass", "top", "ldapSubentry", "subschema");
1196
1197        if (schemaEntry == null)
1198        {
1199          schemaEntry = e;
1200        }
1201        else
1202        {
1203          for (final Attribute a : e.getAttributes())
1204          {
1205            schemaEntry.addAttribute(a);
1206          }
1207        }
1208      }
1209      finally
1210      {
1211        ldifReader.close();
1212      }
1213    }
1214
1215    if (schemaEntry == null)
1216    {
1217      return null;
1218    }
1219
1220    if (throwOnUnparsableElement)
1221    {
1222      try
1223      {
1224        return parseSchemaEntry(schemaEntry);
1225      }
1226      catch (final LDAPException e)
1227      {
1228        Debug.debugException(e);
1229        throw new LDIFException(e.getMessage(), 0, false, e);
1230      }
1231    }
1232    else
1233    {
1234      return new Schema(schemaEntry);
1235    }
1236  }
1237
1238
1239
1240  /**
1241   * Reads schema information from the provided input stream.  The information
1242   * should be in LDIF form, with the definitions represented in the form
1243   * described in section 4.1 of RFC 4512.  Only a single entry will be read
1244   * from the input stream, and it will be closed at the end of this method.
1245   *
1246   * @param  inputStream  The input stream from which the schema entry will be
1247   *                      read.  It must not be {@code null}, and it will be
1248   *                      closed when this method returns.
1249   *
1250   * @return  The schema read from the provided input stream, or {@code null} if
1251   *          the end of the input stream is reached without reading any data.
1252   *
1253   * @throws  IOException  If a problem is encountered while attempting to read
1254   *                       from the provided input stream.
1255   *
1256   * @throws  LDIFException  If a problem occurs while attempting to parse the
1257   *                         data read as LDIF.
1258   */
1259  public static Schema getSchema(final InputStream inputStream)
1260         throws IOException, LDIFException
1261  {
1262    Validator.ensureNotNull(inputStream);
1263
1264    final LDIFReader ldifReader = new LDIFReader(inputStream);
1265
1266    try
1267    {
1268      final Entry e = ldifReader.readEntry();
1269      if (e == null)
1270      {
1271        return null;
1272      }
1273      else
1274      {
1275        return new Schema(e);
1276      }
1277    }
1278    finally
1279    {
1280      ldifReader.close();
1281    }
1282  }
1283
1284
1285
1286  /**
1287   * Retrieves a schema object that contains definitions for a number of
1288   * standard attribute types and object classes from LDAP-related RFCs and
1289   * Internet Drafts.
1290   *
1291   * @return  A schema object that contains definitions for a number of standard
1292   *          attribute types and object classes from LDAP-related RFCs and
1293   *          Internet Drafts.
1294   *
1295   * @throws  LDAPException  If a problem occurs while attempting to obtain or
1296   *                         parse the default standard schema definitions.
1297   */
1298  public static Schema getDefaultStandardSchema()
1299         throws LDAPException
1300  {
1301    final Schema s = DEFAULT_STANDARD_SCHEMA.get();
1302    if (s != null)
1303    {
1304      return s;
1305    }
1306
1307    synchronized (DEFAULT_STANDARD_SCHEMA)
1308    {
1309      try
1310      {
1311        final ClassLoader classLoader = Schema.class.getClassLoader();
1312        final InputStream inputStream =
1313             classLoader.getResourceAsStream(DEFAULT_SCHEMA_RESOURCE_PATH);
1314        final LDIFReader ldifReader = new LDIFReader(inputStream);
1315        final Entry schemaEntry = ldifReader.readEntry();
1316        ldifReader.close();
1317
1318        final Schema schema = new Schema(schemaEntry);
1319        DEFAULT_STANDARD_SCHEMA.set(schema);
1320        return schema;
1321      }
1322      catch (final Exception e)
1323      {
1324        Debug.debugException(e);
1325        throw new LDAPException(ResultCode.LOCAL_ERROR,
1326             ERR_SCHEMA_CANNOT_LOAD_DEFAULT_DEFINITIONS.get(
1327                  StaticUtils.getExceptionMessage(e)),
1328             e);
1329      }
1330    }
1331  }
1332
1333
1334
1335  /**
1336   * Retrieves a schema containing all of the elements of each of the provided
1337   * schemas.
1338   *
1339   * @param  schemas  The schemas to be merged.  It must not be {@code null} or
1340   *                  empty.
1341   *
1342   * @return  A merged representation of the provided schemas.
1343   */
1344  public static Schema mergeSchemas(final Schema... schemas)
1345  {
1346    if ((schemas == null) || (schemas.length == 0))
1347    {
1348      return null;
1349    }
1350    else if (schemas.length == 1)
1351    {
1352      return schemas[0];
1353    }
1354
1355    final LinkedHashMap<String,String> asMap =
1356         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
1357    final LinkedHashMap<String,String> atMap =
1358         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
1359    final LinkedHashMap<String,String> dcrMap =
1360         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1361    final LinkedHashMap<Integer,String> dsrMap =
1362         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1363    final LinkedHashMap<String,String> mrMap =
1364         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
1365    final LinkedHashMap<String,String> mruMap =
1366         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1367    final LinkedHashMap<String,String> nfMap =
1368         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1369    final LinkedHashMap<String,String> ocMap =
1370         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
1371
1372    for (final Schema s : schemas)
1373    {
1374      for (final AttributeSyntaxDefinition as : s.asSet)
1375      {
1376        asMap.put(StaticUtils.toLowerCase(as.getOID()), as.toString());
1377      }
1378
1379      for (final AttributeTypeDefinition at : s.atSet)
1380      {
1381        atMap.put(StaticUtils.toLowerCase(at.getOID()), at.toString());
1382      }
1383
1384      for (final DITContentRuleDefinition dcr : s.dcrSet)
1385      {
1386        dcrMap.put(StaticUtils.toLowerCase(dcr.getOID()), dcr.toString());
1387      }
1388
1389      for (final DITStructureRuleDefinition dsr : s.dsrSet)
1390      {
1391        dsrMap.put(dsr.getRuleID(), dsr.toString());
1392      }
1393
1394      for (final MatchingRuleDefinition mr : s.mrSet)
1395      {
1396        mrMap.put(StaticUtils.toLowerCase(mr.getOID()), mr.toString());
1397      }
1398
1399      for (final MatchingRuleUseDefinition mru : s.mruSet)
1400      {
1401        mruMap.put(StaticUtils.toLowerCase(mru.getOID()), mru.toString());
1402      }
1403
1404      for (final NameFormDefinition nf : s.nfSet)
1405      {
1406        nfMap.put(StaticUtils.toLowerCase(nf.getOID()), nf.toString());
1407      }
1408
1409      for (final ObjectClassDefinition oc : s.ocSet)
1410      {
1411        ocMap.put(StaticUtils.toLowerCase(oc.getOID()), oc.toString());
1412      }
1413    }
1414
1415    final Entry e = new Entry(schemas[0].getSchemaEntry().getDN());
1416
1417    final Attribute ocAttr =
1418         schemas[0].getSchemaEntry().getObjectClassAttribute();
1419    if (ocAttr == null)
1420    {
1421      e.addAttribute("objectClass", "top", "ldapSubEntry", "subschema");
1422    }
1423    else
1424    {
1425      e.addAttribute(ocAttr);
1426    }
1427
1428    if (! asMap.isEmpty())
1429    {
1430      final String[] values = new String[asMap.size()];
1431      e.addAttribute(ATTR_ATTRIBUTE_SYNTAX, asMap.values().toArray(values));
1432    }
1433
1434    if (! mrMap.isEmpty())
1435    {
1436      final String[] values = new String[mrMap.size()];
1437      e.addAttribute(ATTR_MATCHING_RULE, mrMap.values().toArray(values));
1438    }
1439
1440    if (! atMap.isEmpty())
1441    {
1442      final String[] values = new String[atMap.size()];
1443      e.addAttribute(ATTR_ATTRIBUTE_TYPE, atMap.values().toArray(values));
1444    }
1445
1446    if (! ocMap.isEmpty())
1447    {
1448      final String[] values = new String[ocMap.size()];
1449      e.addAttribute(ATTR_OBJECT_CLASS, ocMap.values().toArray(values));
1450    }
1451
1452    if (! dcrMap.isEmpty())
1453    {
1454      final String[] values = new String[dcrMap.size()];
1455      e.addAttribute(ATTR_DIT_CONTENT_RULE, dcrMap.values().toArray(values));
1456    }
1457
1458    if (! dsrMap.isEmpty())
1459    {
1460      final String[] values = new String[dsrMap.size()];
1461      e.addAttribute(ATTR_DIT_STRUCTURE_RULE, dsrMap.values().toArray(values));
1462    }
1463
1464    if (! nfMap.isEmpty())
1465    {
1466      final String[] values = new String[nfMap.size()];
1467      e.addAttribute(ATTR_NAME_FORM, nfMap.values().toArray(values));
1468    }
1469
1470    if (! mruMap.isEmpty())
1471    {
1472      final String[] values = new String[mruMap.size()];
1473      e.addAttribute(ATTR_MATCHING_RULE_USE, mruMap.values().toArray(values));
1474    }
1475
1476    return new Schema(e);
1477  }
1478
1479
1480
1481  /**
1482   * Retrieves the entry used to create this schema object.
1483   *
1484   * @return  The entry used to create this schema object.
1485   */
1486  public ReadOnlyEntry getSchemaEntry()
1487  {
1488    return schemaEntry;
1489  }
1490
1491
1492
1493  /**
1494   * Retrieves the value of the subschemaSubentry attribute from the specified
1495   * entry using the provided connection.
1496   *
1497   * @param  connection  The connection to use in order to perform the search.
1498   *                     It must not be {@code null}.
1499   * @param  entryDN     The DN of the entry from which to retrieve the
1500   *                     subschemaSubentry attribute.  It may be {@code null} or
1501   *                     an empty string in order to retrieve the value from the
1502   *                     server's root DSE.
1503   *
1504   * @return  The value of the subschemaSubentry attribute from the specified
1505   *          entry, or {@code null} if it is not available for some reason
1506   *          (e.g., the client does not have permission to read the target
1507   *          entry or the subschemaSubentry attribute).
1508   *
1509   * @throws  LDAPException  If a problem occurs while attempting to retrieve
1510   *                         the specified entry.
1511   */
1512  public static String getSubschemaSubentryDN(final LDAPConnection connection,
1513                                              final String entryDN)
1514         throws LDAPException
1515  {
1516    Validator.ensureNotNull(connection);
1517
1518    final Entry e;
1519    if (entryDN == null)
1520    {
1521      e = connection.getEntry("", SUBSCHEMA_SUBENTRY_REQUEST_ATTRS);
1522    }
1523    else
1524    {
1525      e = connection.getEntry(entryDN, SUBSCHEMA_SUBENTRY_REQUEST_ATTRS);
1526    }
1527
1528    if (e == null)
1529    {
1530      return null;
1531    }
1532
1533    return e.getAttributeValue(ATTR_SUBSCHEMA_SUBENTRY);
1534  }
1535
1536
1537
1538  /**
1539   * Retrieves the set of attribute syntax definitions contained in the server
1540   * schema.
1541   *
1542   * @return  The set of attribute syntax definitions contained in the server
1543   *          schema.
1544   */
1545  public Set<AttributeSyntaxDefinition> getAttributeSyntaxes()
1546  {
1547    return asSet;
1548  }
1549
1550
1551
1552  /**
1553   * Retrieves the attribute syntax with the specified OID from the server
1554   * schema.
1555   *
1556   * @param  oid  The OID of the attribute syntax to retrieve.  It must not be
1557   *              {@code null}.  It may optionally include a minimum upper bound
1558   *              (as may appear when the syntax OID is included in an attribute
1559   *              type definition), but if it does then that portion will be
1560   *              ignored when retrieving the attribute syntax.
1561   *
1562   * @return  The requested attribute syntax, or {@code null} if there is no
1563   *          such syntax defined in the server schema.
1564   */
1565  public AttributeSyntaxDefinition getAttributeSyntax(final String oid)
1566  {
1567    Validator.ensureNotNull(oid);
1568
1569    final String lowerOID = StaticUtils.toLowerCase(oid);
1570    final int    curlyPos = lowerOID.indexOf('{');
1571
1572    if (curlyPos > 0)
1573    {
1574      return asMap.get(lowerOID.substring(0, curlyPos));
1575    }
1576    else
1577    {
1578      return asMap.get(lowerOID);
1579    }
1580  }
1581
1582
1583
1584  /**
1585   * Retrieves the set of attribute type definitions contained in the server
1586   * schema.
1587   *
1588   * @return  The set of attribute type definitions contained in the server
1589   *          schema.
1590   */
1591  public Set<AttributeTypeDefinition> getAttributeTypes()
1592  {
1593    return atSet;
1594  }
1595
1596
1597
1598  /**
1599   * Retrieves the set of operational attribute type definitions (i.e., those
1600   * definitions with a usage of directoryOperation, distributedOperation, or
1601   * dSAOperation) contained in the  server  schema.
1602   *
1603   * @return  The set of operational attribute type definitions contained in the
1604   *          server schema.
1605   */
1606  public Set<AttributeTypeDefinition> getOperationalAttributeTypes()
1607  {
1608    return operationalATSet;
1609  }
1610
1611
1612
1613  /**
1614   * Retrieves the set of user attribute type definitions (i.e., those
1615   * definitions with a usage of userApplications) contained in the  server
1616   * schema.
1617   *
1618   * @return  The set of user attribute type definitions contained in the server
1619   *          schema.
1620   */
1621  public Set<AttributeTypeDefinition> getUserAttributeTypes()
1622  {
1623    return userATSet;
1624  }
1625
1626
1627
1628  /**
1629   * Retrieves the attribute type with the specified name or OID from the server
1630   * schema.
1631   *
1632   * @param  name  The name or OID of the attribute type to retrieve.  It must
1633   *               not be {@code null}.
1634   *
1635   * @return  The requested attribute type, or {@code null} if there is no
1636   *          such attribute type defined in the server schema.
1637   */
1638  public AttributeTypeDefinition getAttributeType(final String name)
1639  {
1640    Validator.ensureNotNull(name);
1641
1642    return atMap.get(StaticUtils.toLowerCase(name));
1643  }
1644
1645
1646
1647  /**
1648   * Retrieves a list of all subordinate attribute type definitions for the
1649   * provided attribute type definition.
1650   *
1651   * @param  d  The attribute type definition for which to retrieve all
1652   *            subordinate attribute types.  It must not be {@code null}.
1653   *
1654   * @return  A list of all subordinate attribute type definitions for the
1655   *          provided attribute type definition, or an empty list if it does
1656   *          not have any subordinate types or the provided attribute type is
1657   *          not defined in the schema.
1658   */
1659  public List<AttributeTypeDefinition> getSubordinateAttributeTypes(
1660                                            final AttributeTypeDefinition d)
1661  {
1662    Validator.ensureNotNull(d);
1663
1664    final List<AttributeTypeDefinition> l = subordinateAttributeTypes.get(d);
1665    if (l == null)
1666    {
1667      return Collections.emptyList();
1668    }
1669    else
1670    {
1671      return Collections.unmodifiableList(l);
1672    }
1673  }
1674
1675
1676
1677  /**
1678   * Retrieves the set of DIT content rule definitions contained in the server
1679   * schema.
1680   *
1681   * @return  The set of DIT content rule definitions contained in the server
1682   *          schema.
1683   */
1684  public Set<DITContentRuleDefinition> getDITContentRules()
1685  {
1686    return dcrSet;
1687  }
1688
1689
1690
1691  /**
1692   * Retrieves the DIT content rule with the specified name or OID from the
1693   * server schema.
1694   *
1695   * @param  name  The name or OID of the DIT content rule to retrieve.  It must
1696   *               not be {@code null}.
1697   *
1698   * @return  The requested DIT content rule, or {@code null} if there is no
1699   *          such rule defined in the server schema.
1700   */
1701  public DITContentRuleDefinition getDITContentRule(final String name)
1702  {
1703    Validator.ensureNotNull(name);
1704
1705    return dcrMap.get(StaticUtils.toLowerCase(name));
1706  }
1707
1708
1709
1710  /**
1711   * Retrieves the set of DIT structure rule definitions contained in the server
1712   * schema.
1713   *
1714   * @return  The set of DIT structure rule definitions contained in the server
1715   *          schema.
1716   */
1717  public Set<DITStructureRuleDefinition> getDITStructureRules()
1718  {
1719    return dsrSet;
1720  }
1721
1722
1723
1724  /**
1725   * Retrieves the DIT content rule with the specified rule ID from the server
1726   * schema.
1727   *
1728   * @param  ruleID  The rule ID for the DIT structure rule to retrieve.
1729   *
1730   * @return  The requested DIT structure rule, or {@code null} if there is no
1731   *          such rule defined in the server schema.
1732   */
1733  public DITStructureRuleDefinition getDITStructureRuleByID(final int ruleID)
1734  {
1735    return dsrMapByID.get(ruleID);
1736  }
1737
1738
1739
1740  /**
1741   * Retrieves the DIT content rule with the specified name from the server
1742   * schema.
1743   *
1744   * @param  ruleName  The name of the DIT structure rule to retrieve.  It must
1745   *                   not be {@code null}.
1746   *
1747   * @return  The requested DIT structure rule, or {@code null} if there is no
1748   *          such rule defined in the server schema.
1749   */
1750  public DITStructureRuleDefinition getDITStructureRuleByName(
1751                                         final String ruleName)
1752  {
1753    Validator.ensureNotNull(ruleName);
1754
1755    return dsrMapByName.get(StaticUtils.toLowerCase(ruleName));
1756  }
1757
1758
1759
1760  /**
1761   * Retrieves the DIT content rule associated with the specified name form from
1762   * the server schema.
1763   *
1764   * @param  nameForm  The name or OID of the name form for which to retrieve
1765   *                   the associated DIT structure rule.
1766   *
1767   * @return  The requested DIT structure rule, or {@code null} if there is no
1768   *          such rule defined in the server schema.
1769   */
1770  public DITStructureRuleDefinition getDITStructureRuleByNameForm(
1771                                         final String nameForm)
1772  {
1773    Validator.ensureNotNull(nameForm);
1774
1775    return dsrMapByNameForm.get(StaticUtils.toLowerCase(nameForm));
1776  }
1777
1778
1779
1780  /**
1781   * Retrieves the set of matching rule definitions contained in the server
1782   * schema.
1783   *
1784   * @return  The set of matching rule definitions contained in the server
1785   *          schema.
1786   */
1787  public Set<MatchingRuleDefinition> getMatchingRules()
1788  {
1789    return mrSet;
1790  }
1791
1792
1793
1794  /**
1795   * Retrieves the matching rule with the specified name or OID from the server
1796   * schema.
1797   *
1798   * @param  name  The name or OID of the matching rule to retrieve.  It must
1799   *               not be {@code null}.
1800   *
1801   * @return  The requested matching rule, or {@code null} if there is no
1802   *          such rule defined in the server schema.
1803   */
1804  public MatchingRuleDefinition getMatchingRule(final String name)
1805  {
1806    Validator.ensureNotNull(name);
1807
1808    return mrMap.get(StaticUtils.toLowerCase(name));
1809  }
1810
1811
1812
1813  /**
1814   * Retrieves the set of matching rule use definitions contained in the server
1815   * schema.
1816   *
1817   * @return  The set of matching rule use definitions contained in the server
1818   *          schema.
1819   */
1820  public Set<MatchingRuleUseDefinition> getMatchingRuleUses()
1821  {
1822    return mruSet;
1823  }
1824
1825
1826
1827  /**
1828   * Retrieves the matching rule use with the specified name or OID from the
1829   * server schema.
1830   *
1831   * @param  name  The name or OID of the matching rule use to retrieve.  It
1832   *               must not be {@code null}.
1833   *
1834   * @return  The requested matching rule, or {@code null} if there is no
1835   *          such matching rule use defined in the server schema.
1836   */
1837  public MatchingRuleUseDefinition getMatchingRuleUse(final String name)
1838  {
1839    Validator.ensureNotNull(name);
1840
1841    return mruMap.get(StaticUtils.toLowerCase(name));
1842  }
1843
1844
1845
1846  /**
1847   * Retrieves the set of name form definitions contained in the server schema.
1848   *
1849   * @return  The set of name form definitions contained in the server schema.
1850   */
1851  public Set<NameFormDefinition> getNameForms()
1852  {
1853    return nfSet;
1854  }
1855
1856
1857
1858  /**
1859   * Retrieves the name form with the specified name or OID from the server
1860   * schema.
1861   *
1862   * @param  name  The name or OID of the name form to retrieve.  It must not be
1863   *               {@code null}.
1864   *
1865   * @return  The requested name form, or {@code null} if there is no
1866   *          such rule defined in the server schema.
1867   */
1868  public NameFormDefinition getNameFormByName(final String name)
1869  {
1870    Validator.ensureNotNull(name);
1871
1872    return nfMapByName.get(StaticUtils.toLowerCase(name));
1873  }
1874
1875
1876
1877  /**
1878   * Retrieves the name form associated with the specified structural object
1879   * class from the server schema.
1880   *
1881   * @param  objectClass  The name or OID of the structural object class for
1882   *                      which to retrieve the associated name form.  It must
1883   *                      not be {@code null}.
1884   *
1885   * @return  The requested name form, or {@code null} if there is no
1886   *          such rule defined in the server schema.
1887   */
1888  public NameFormDefinition getNameFormByObjectClass(final String objectClass)
1889  {
1890    Validator.ensureNotNull(objectClass);
1891
1892    return nfMapByOC.get(StaticUtils.toLowerCase(objectClass));
1893  }
1894
1895
1896
1897  /**
1898   * Retrieves the set of object class definitions contained in the server
1899   * schema.
1900   *
1901   * @return  The set of object class definitions contained in the server
1902   *          schema.
1903   */
1904  public Set<ObjectClassDefinition> getObjectClasses()
1905  {
1906    return ocSet;
1907  }
1908
1909
1910
1911  /**
1912   * Retrieves the set of abstract object class definitions contained in the
1913   * server schema.
1914   *
1915   * @return  The set of abstract object class definitions contained in the
1916   *          server schema.
1917   */
1918  public Set<ObjectClassDefinition> getAbstractObjectClasses()
1919  {
1920    return abstractOCSet;
1921  }
1922
1923
1924
1925  /**
1926   * Retrieves the set of auxiliary object class definitions contained in the
1927   * server schema.
1928   *
1929   * @return  The set of auxiliary object class definitions contained in the
1930   *          server schema.
1931   */
1932  public Set<ObjectClassDefinition> getAuxiliaryObjectClasses()
1933  {
1934    return auxiliaryOCSet;
1935  }
1936
1937
1938
1939  /**
1940   * Retrieves the set of structural object class definitions contained in the
1941   * server schema.
1942   *
1943   * @return  The set of structural object class definitions contained in the
1944   *          server schema.
1945   */
1946  public Set<ObjectClassDefinition> getStructuralObjectClasses()
1947  {
1948    return structuralOCSet;
1949  }
1950
1951
1952
1953  /**
1954   * Retrieves the object class with the specified name or OID from the server
1955   * schema.
1956   *
1957   * @param  name  The name or OID of the object class to retrieve.  It must
1958   *               not be {@code null}.
1959   *
1960   * @return  The requested object class, or {@code null} if there is no such
1961   *          class defined in the server schema.
1962   */
1963  public ObjectClassDefinition getObjectClass(final String name)
1964  {
1965    Validator.ensureNotNull(name);
1966
1967    return ocMap.get(StaticUtils.toLowerCase(name));
1968  }
1969
1970
1971
1972  /**
1973   * Retrieves a hash code for this schema object.
1974   *
1975   * @return  A hash code for this schema object.
1976   */
1977  @Override()
1978  public int hashCode()
1979  {
1980    int hc;
1981    try
1982    {
1983      hc = schemaEntry.getParsedDN().hashCode();
1984    }
1985    catch (final Exception e)
1986    {
1987      Debug.debugException(e);
1988      hc = StaticUtils.toLowerCase(schemaEntry.getDN()).hashCode();
1989    }
1990
1991    Attribute a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_SYNTAX);
1992    if (a != null)
1993    {
1994      hc += a.hashCode();
1995    }
1996
1997    a = schemaEntry.getAttribute(ATTR_MATCHING_RULE);
1998    if (a != null)
1999    {
2000      hc += a.hashCode();
2001    }
2002
2003    a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_TYPE);
2004    if (a != null)
2005    {
2006      hc += a.hashCode();
2007    }
2008
2009    a = schemaEntry.getAttribute(ATTR_OBJECT_CLASS);
2010    if (a != null)
2011    {
2012      hc += a.hashCode();
2013    }
2014
2015    a = schemaEntry.getAttribute(ATTR_NAME_FORM);
2016    if (a != null)
2017    {
2018      hc += a.hashCode();
2019    }
2020
2021    a = schemaEntry.getAttribute(ATTR_DIT_CONTENT_RULE);
2022    if (a != null)
2023    {
2024      hc += a.hashCode();
2025    }
2026
2027    a = schemaEntry.getAttribute(ATTR_DIT_STRUCTURE_RULE);
2028    if (a != null)
2029    {
2030      hc += a.hashCode();
2031    }
2032
2033    a = schemaEntry.getAttribute(ATTR_MATCHING_RULE_USE);
2034    if (a != null)
2035    {
2036      hc += a.hashCode();
2037    }
2038
2039    return hc;
2040  }
2041
2042
2043
2044  /**
2045   * Indicates whether the provided object is equal to this schema object.
2046   *
2047   * @param  o  The object for which to make the determination.
2048   *
2049   * @return  {@code true} if the provided object is equal to this schema
2050   *          object, or {@code false} if not.
2051   */
2052  @Override()
2053  public boolean equals(final Object o)
2054  {
2055    if (o == null)
2056    {
2057      return false;
2058    }
2059
2060    if (o == this)
2061    {
2062      return true;
2063    }
2064
2065    if (! (o instanceof Schema))
2066    {
2067      return false;
2068    }
2069
2070    final Schema s = (Schema) o;
2071
2072    try
2073    {
2074      if (! schemaEntry.getParsedDN().equals(s.schemaEntry.getParsedDN()))
2075      {
2076        return false;
2077      }
2078    }
2079    catch (final Exception e)
2080    {
2081      Debug.debugException(e);
2082      if (! schemaEntry.getDN().equalsIgnoreCase(s.schemaEntry.getDN()))
2083      {
2084        return false;
2085      }
2086    }
2087
2088    return (asSet.equals(s.asSet) &&
2089         mrSet.equals(s.mrSet) &&
2090         atSet.equals(s.atSet) &&
2091         ocSet.equals(s.ocSet) &&
2092         nfSet.equals(s.nfSet) &&
2093         dcrSet.equals(s.dcrSet) &&
2094         dsrSet.equals(s.dsrSet) &&
2095         mruSet.equals(s.mruSet));
2096  }
2097
2098
2099
2100  /**
2101   * Retrieves a string representation of the associated schema entry.
2102   *
2103   * @return  A string representation of the associated schema entry.
2104   */
2105  @Override()
2106  public String toString()
2107  {
2108    return schemaEntry.toString();
2109  }
2110}