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