001/*
002 * Copyright 2020-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-2024 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) 2020-2024 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.text.ParseException;
043import java.util.ArrayList;
044import java.util.Collection;
045import java.util.Collections;
046import java.util.EnumSet;
047import java.util.HashMap;
048import java.util.HashSet;
049import java.util.Iterator;
050import java.util.LinkedHashMap;
051import java.util.LinkedHashSet;
052import java.util.List;
053import java.util.Map;
054import java.util.Set;
055import java.util.TreeMap;
056import java.util.concurrent.atomic.AtomicInteger;
057import java.util.regex.Pattern;
058
059import com.unboundid.ldap.sdk.Entry;
060import com.unboundid.ldap.sdk.InternalSDKHelper;
061import com.unboundid.ldap.sdk.LDAPException;
062import com.unboundid.ldif.LDIFException;
063import com.unboundid.ldif.LDIFReader;
064import com.unboundid.util.Debug;
065import com.unboundid.util.Mutable;
066import com.unboundid.util.NotNull;
067import com.unboundid.util.Nullable;
068import com.unboundid.util.OID;
069import com.unboundid.util.StaticUtils;
070import com.unboundid.util.ThreadSafety;
071import com.unboundid.util.ThreadSafetyLevel;
072import com.unboundid.util.Validator;
073
074import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
075
076
077
078/**
079 * This class provides a mechanism form validating definitions in an LDAP
080 * schema.  Schema elements are expected to be read from one or more LDIF files
081 * containing subschema subentries as described in
082 * <A HREF="http://www.ietf.org/rfc/rfc4512.txt">RFC 4512</A> section 4.2, with
083 * elements defined using the syntaxes described in section 4.1 of the same
084 * specification.
085 * <BR><BR>
086 * This schema validator can perform the following checks:
087 * <UL>
088 *   <LI>It ensures that each schema element can be parsed.</LI>
089 *   <LI>It ensures that element names and OIDs are properly formed, optionally
090 *       allowing for more lax validation that some servers may permit.</LI>
091 *   <LI>It ensures that each schema element does not reference any undefined
092 *       schema elements.</LI>
093 *   <LI>It can verify that each element is only defined once.</LI>
094 *   <LI>It can optionally determine whether definitions may use functionality
095 *       that some servers do not support.</LI>
096 *   <LI>It can verify that schema entries are valid in accordance with the
097 *       schema it contains.</LI>
098 *   <LI>It can optionally ensure that schema files are named using an
099 *       expected pattern.</LI>
100 *   <LI>It can optionally validate extensions used in schema elements.</LI>
101 * </UL>
102 *
103 * It ensures that all definitions can be parsed, contain valid
104 * content, do not reference any undefined schema elements, etc.
105 */
106@Mutable()
107@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
108public final class SchemaValidator
109{
110  /**
111   * Indicates whether an instance of the Ping Identity Directory Server is
112   * available.
113   */
114  static final boolean PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE;
115
116
117
118  /**
119   * The path to the schema directory for the available Ping Identity Directory
120   * Server instance.
121   */
122  @Nullable static final File PING_IDENTITY_DIRECTORY_SERVER_SCHEMA_DIR;
123
124
125
126  static
127  {
128    boolean pingIdentityDSAvailable = false;
129    File schemaDir = null;
130
131    try
132    {
133      final File instanceRoot = InternalSDKHelper.getPingIdentityServerRoot();
134      if (instanceRoot != null)
135      {
136        final File instanceRootSchemaDir =
137             StaticUtils.constructPath(instanceRoot, "config", "schema");
138        if (new File(instanceRootSchemaDir, "00-core.ldif").exists())
139        {
140          // Try to see if we can load the server's schema class.  If so, then
141          // we'll assume that we are running with access to a Ping Identity
142          // Directory Server, and we'll tailor the defaults accordingly.
143          // If this fails, we'll just go with the defaults.
144          Class.forName("com.unboundid.directory.server.types.Schema");
145
146          pingIdentityDSAvailable = true;
147          schemaDir = instanceRootSchemaDir;
148        }
149      }
150    }
151    catch (final Throwable t)
152    {
153      // This is fine.  We're just not running with access to a Ping Identity
154      // Directory Server.
155    }
156
157    PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE = pingIdentityDSAvailable;
158    PING_IDENTITY_DIRECTORY_SERVER_SCHEMA_DIR = schemaDir;
159  }
160
161
162
163  // Indicates whether to allow attribute type definitions that do not include
164  // an equality matching rule and do not include a superior type.
165  private boolean allowAttributeTypesWithoutEqualityMatchingRule;
166
167  // Indicates whether to allow attribute type definitions that do not include
168  // an attribute syntax OID and do not include a superior type.
169  private boolean allowAttributeTypesWithoutSyntax;
170
171  // Indicates whether to allow attribute type definitions that are defined with
172  // the COLLECTIVE indicator.
173  private boolean allowCollectiveAttributes;
174
175  // Indicates whether to allow schema elements that do not have names.
176  private boolean allowElementsWithoutNames;
177
178  // Indicates whether to allow schema elements to contain an empty DESC
179  // component.
180  private boolean allowEmptyDescription;
181
182  // Indicates whether to allow invalid object class inheritance.
183  private boolean allowInvalidObjectClassInheritance;
184
185  // Indicates whether to allow a single schema file to contain multiple
186  // entries.
187  private boolean allowMultipleEntriesPerFile;
188
189  // Indicates whether to allow object classes to contain multiple superior
190  // classes.
191  private boolean allowMultipleSuperiorObjectClasses;
192
193  // Indicates whether to allow LDAP names to start with a digit.
194  private boolean allowNamesWithInitialDigit;
195
196  // Indicates whether to allow LDAP names to start with a hyphen.
197  private boolean allowNamesWithInitialHyphen;
198
199  // Indicates whether to allow LDAP names to contain the underscore character.
200  private boolean allowNamesWithUnderscore;
201
202  // Indicates whether to allow schema elements to contain non-numeric OIDs that
203  // are not of the form "name-oid".
204  private boolean allowNonNumericOIDsNotUsingName;
205
206  // Indicates whether to allow schema elements to contain non-numeric OIDs that
207  // are of the form "name-oid".
208  private boolean allowNonNumericOIDsUsingName;
209
210  // Indicates whether to allow attribute type definitions that are defined with
211  // the OBSOLETE indicator.
212  private boolean allowObsoleteElements;
213
214  // Indicates whether to allow multiple definitions for the same schema
215  // element.
216  private boolean allowRedefiningElements;
217
218  // Indicates whether to support a schema directory with schema files in
219  // subdirectories.
220  private boolean allowSchemaFilesInSubDirectories;
221
222  // Indicates whether to allow structural object classes that do not define a
223  // superior class.
224  private boolean allowStructuralObjectClassWithoutSuperior;
225
226  // Indicates whether to ensure that the entries containing schema definitions
227  // are themselves valid in accordance with the schema elements that have been
228  // read.
229  private boolean ensureSchemaEntryIsValid;
230
231  // Indicates whether to ignore files contained in a schema directory that do
232  // not match the configured file name pattern.
233  private boolean ignoreSchemaFilesNotMatchingFileNamePattern;
234
235  // Indicates whether to use strict validation when examining numeric OIDs.
236  private boolean useStrictOIDValidation;
237
238  // A list of attribute syntax definitions to use when validating attribute
239  // type definitions.
240  @NotNull private List<AttributeSyntaxDefinition> attributeSyntaxList;
241
242  // A list of matching rule definitions to use when validating attribute type
243  // definitions.
244  @NotNull private List<MatchingRuleDefinition> matchingRuleList;
245
246  // A map of attribute syntax definitions to use when validating attribute type
247  // definitions.
248  @NotNull private Map<String,AttributeSyntaxDefinition> attributeSyntaxMap;
249
250  // A map of matching rule definitions to use when validating attribute type
251  // definitions.
252  @NotNull private Map<String,MatchingRuleDefinition> matchingRuleMap;
253
254  // The pattern that files containing schema definitions are expected to match.
255  @Nullable private Pattern schemaFileNamePattern;
256
257  // The set of schema element types that are allowed to be present in schema
258  // files.
259  @NotNull private final Set<SchemaElementType> allowedSchemaElementTypes;
260
261  // The set of schema element types that other elements may be allowed to
262  // reference without the referenced type being defined.
263  @NotNull private final Set<SchemaElementType>
264       allowReferencesToUndefinedElementTypes;
265
266
267
268  /**
269   * Creates a new schema validator with the default settings.
270   */
271  public SchemaValidator()
272  {
273    allowAttributeTypesWithoutEqualityMatchingRule = true;
274    allowAttributeTypesWithoutSyntax = PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE;
275    allowCollectiveAttributes = (! PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE);
276    allowElementsWithoutNames = true;
277    allowEmptyDescription = false;
278    allowInvalidObjectClassInheritance = false;
279    allowMultipleEntriesPerFile = false;
280    allowMultipleSuperiorObjectClasses =
281         (! PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE);
282    allowNamesWithInitialDigit = false;
283    allowNamesWithInitialHyphen = false;
284    allowNamesWithUnderscore = false;
285    allowNonNumericOIDsNotUsingName = false;
286    allowNonNumericOIDsUsingName = false;
287    allowObsoleteElements = true;
288    allowRedefiningElements = false;
289    allowSchemaFilesInSubDirectories = false;
290    allowStructuralObjectClassWithoutSuperior = false;
291    ensureSchemaEntryIsValid = true;
292    ignoreSchemaFilesNotMatchingFileNamePattern =
293         (! PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE);
294    useStrictOIDValidation = true;
295    attributeSyntaxMap = new LinkedHashMap<>();
296    attributeSyntaxList = new ArrayList<>();
297    matchingRuleMap = new LinkedHashMap<>();
298    matchingRuleList = new ArrayList<>();
299    schemaFileNamePattern = null;
300    allowedSchemaElementTypes = EnumSet.allOf(SchemaElementType.class);
301    allowReferencesToUndefinedElementTypes =
302         EnumSet.noneOf(SchemaElementType.class);
303
304    if (PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE)
305    {
306      configureLDAPSDKDefaultAttributeSyntaxes();
307      configureLDAPSDKDefaultMatchingRules();
308
309      schemaFileNamePattern = Pattern.compile("^\\d\\d-.+\\.ldif$");
310    }
311  }
312
313
314
315  /**
316   * Retrieves the pattern that schema file names are expected to match.  By
317   * default, no schema file name pattern is defined, so there are no
318   * restrictions on the name that a schema file may have.
319   *
320   * @return  The pattern that schema file names are expected to match, or
321   *          {@code null} if no schema file name pattern is defined.
322   */
323  @Nullable()
324  public Pattern getSchemaFileNamePattern()
325  {
326    return schemaFileNamePattern;
327  }
328
329
330
331  /**
332   * Indicates whether to ignore any files in a schema directory that do not
333   * match the value pattern (if one is defined).  By default, if a file name
334   * pattern is defined, then any files whose names do not match that pattern
335   * will be ignored.
336   *
337   * @return  {@code true} if files not matching the defined value pattern
338   *          should be ignored, or {@code false} if they should not be ignored
339   *          but a warning should be generated.
340   */
341  public boolean ignoreSchemaFilesNotMatchingFileNamePattern()
342  {
343    return ignoreSchemaFilesNotMatchingFileNamePattern;
344  }
345
346
347
348  /**
349   * Specifies a pattern that may be used to indicate which files should be
350   * examined if a provided path is a directory rather than a file.
351   *
352   * @param  schemaFileNamePattern
353   *              A regular expression that may be used to specify the pattern
354   *              that schema file names are expected to match.  This may be
355   *              {@code null} if no pattern is defined and any identified files
356   *              will be processed.
357   * @param  ignoreSchemaFilesNotMatchingFileNamePattern
358   *              Specifies whether to ignore any files in a schema directory
359   *              that do not match the value pattern (if one is defined).  This
360   *              will only have an effect when attempting to parse schema
361   *              definitions from a path that references a directory rather
362   *              than a file.  If this is {@code true} then any files that do
363   *              not match the pattern (if it is non-{@code null} will be
364   *              skipped.  If this is {@code false}, then all files will be
365   *              parsed even if they do not match the value pattern, but a
366   *              warning will be added to the message list for each file that
367   *              does not match the pattern.  If the path provided to the
368   *              {@link #validateSchema} method refers to a file rather than a
369   *              directory, then the file will always be processed, but a
370   *              warning message will be added to the given list if the name
371   *              does not match the given pattern.
372   */
373  public void setSchemaFileNamePattern(
374                   @Nullable final Pattern schemaFileNamePattern,
375                   final boolean ignoreSchemaFilesNotMatchingFileNamePattern)
376  {
377    this.schemaFileNamePattern = schemaFileNamePattern;
378    this.ignoreSchemaFilesNotMatchingFileNamePattern =
379         ignoreSchemaFilesNotMatchingFileNamePattern;
380  }
381
382
383
384  /**
385   * Indicates whether a schema file is allowed to contain multiple entries.
386   * By default, each schema file is expected to have exactly one entry.
387   *
388   * @return  {@code true} if a schema file may have multiple entries and all
389   *          entries contained in that file should be processed, or
390   *          {@code false} if a schema file should only have a single entry
391   *          and additional entries will be ignored and an error message
392   *          reported.
393   */
394  public boolean allowMultipleEntriesPerFile()
395  {
396    return allowMultipleEntriesPerFile;
397  }
398
399
400
401  /**
402   * Specifies whether a schema file is allowed to contain multiple entries.
403   *
404   * @param  allowMultipleEntriesPerFile
405   *              Indicates whether a schema file is allowed to contain multiple
406   *              entries.  If this is {@code true}, then all entries in each
407   *              file will be examined.  If this is {@code false}, then only
408   *              the first entry in each file will be examined and an error
409   *              message will be reported for each file that contains multiple
410   *              entries.  In either case, an error will be reported for each
411   *              schema file that does not contain any entries or that contains
412   *              a malformed entry.
413   */
414  public void setAllowMultipleEntriesPerFile(
415                   final boolean allowMultipleEntriesPerFile)
416  {
417    this.allowMultipleEntriesPerFile = allowMultipleEntriesPerFile;
418  }
419
420
421
422  /**
423   * Indicates whether to examine files in subdirectories when provided with a
424   * schema path that is a directory.  By default, subdirectories will not be
425   * examined and an error will be reported for each subdirectory that is
426   * encountered.
427   *
428   * @return  {@code true} to examine files in subdirectories when provided with
429   *          a schema path that is a directory, or {@code false} if only
430   *          files directly contained in the provided directory will be
431   *          examined and an error will be reported if the schema directory
432   *          contains subdirectories.
433   */
434  public boolean allowSchemaFilesInSubDirectories()
435  {
436    return allowSchemaFilesInSubDirectories;
437  }
438
439
440
441  /**
442   * Specifies whether to examine files in subdirectories when provided with a
443   * schema path that is a directory.  This setting will be ignored if the
444   * {@link #validateSchema} method is called with a schema path that is a file
445   * rather than a directory.
446   *
447   * @param  allowSchemaFilesInSubDirectories
448   *              Indicates whether to examine files in subdirectories when
449   *              provided with a schema path that is a directory.  If this is
450   *              {@code true}, then files in subdirectories will be examined,
451   *              to any depth, with files in the directory processed first
452   *              (in lexicographic order by name) and then subdirectories will
453   *              be processed (also in lexicographic order by name).  if this
454   *              is {@code false}, then only files contained directly in the
455   *              specified schema directory will be examined and an error
456   *              will be reported for each subdirectory that is encountered.
457   */
458  public void setAllowSchemaFilesInSubDirectories(
459                   final boolean allowSchemaFilesInSubDirectories)
460  {
461    this.allowSchemaFilesInSubDirectories = allowSchemaFilesInSubDirectories;
462  }
463
464
465
466  /**
467   * Indicates whether to validate each entry containing the schema definitions
468   * using the schema that has been parsed thus far.  By default, each entry
469   * will be validated to ensure that its contents conform to the schema that
470   * has been parsed from that file and all previous files.
471   *
472   * @return  {@code true} if entries containing schema definitions should be
473   *          validated according to the schema, or {@code false} if schema
474   *          entries will not themselves be validated against the schema.
475   */
476  public boolean ensureSchemaEntryIsValid()
477  {
478    return ensureSchemaEntryIsValid;
479  }
480
481
482
483  /**
484   * Specifies whether to validate each entry containing the schema definitions
485   * using the schema that has been parsed thus far.
486   *
487   * @param  ensureSchemaEntryIsValid
488   *              Indicates whether to validate each entry containing the schema
489   *              definitions using the schema that has been parsed thus far.
490   *              If this is {@code true}, then each entry will be validated
491   *              to ensure that it conforms to the schema definitions read from
492   *              that file and any previuos files that have already been
493   *              processed and any errors identified will be reported  If this
494   *              is {@code false}, then schema entries will not be validated.
495   */
496  public void setEnsureSchemaEntryIsValid(
497                   final boolean ensureSchemaEntryIsValid)
498  {
499    this.ensureSchemaEntryIsValid = ensureSchemaEntryIsValid;
500  }
501
502
503
504  /**
505   * Retrieves an unmodifiable set of the schema element types that may be
506   * defined in schema files.  By default, all types of schema elements may be
507   * defined.
508   *
509   * @return  An unmodifiable set set of the schema element types that may be
510   *          defined in schema files.
511   */
512  @NotNull()
513  public Set<SchemaElementType> getAllowedSchemaElementTypes()
514  {
515    return Collections.unmodifiableSet(allowedSchemaElementTypes);
516  }
517
518
519
520  /**
521   * Specifies the set of schema element types that may be defined in schema
522   * files.
523   *
524   * @param  allowedSchemaElementTypes
525   *              The set of schema element types that may be defined in schema
526   *              files.  It must not be {@code null} or empty.
527   */
528  public void setAllowedSchemaElementTypes(
529       @NotNull final SchemaElementType... allowedSchemaElementTypes)
530  {
531    setAllowedSchemaElementTypes(StaticUtils.toList(allowedSchemaElementTypes));
532  }
533
534
535
536  /**
537   * Specifies the set of schema element types that may be defined in schema
538   * files.
539   *
540   * @param  allowedSchemaElementTypes
541   *              The set of schema element types that may be defined in schema
542   *              files.  It must not be {@code null} or empty.
543   */
544  public void setAllowedSchemaElementTypes(
545       @NotNull final Collection<SchemaElementType> allowedSchemaElementTypes)
546  {
547    Validator.ensureTrue(
548         ((allowedSchemaElementTypes != null) &&
549              (! allowedSchemaElementTypes.isEmpty())),
550         "SchemaValidator.allowedSchemaElementTypes must not be null or " +
551              "empty.");
552
553    this.allowedSchemaElementTypes.clear();
554    this.allowedSchemaElementTypes.addAll(allowedSchemaElementTypes);
555  }
556
557
558
559  /**
560   * Retrieves the types of schema elements that can be referenced by other
561   * elements without the referenced types being known to the schema validator
562   * (e.g., by having been previously defined in the schema files).  By default,
563   * no types of undefined elements may be referenced.
564   *
565   * @return  The types of schema elements that can be referenced by other
566   *          elements without the referenced types being known to the schema
567   *          validator,
568   */
569  @NotNull()
570  public Set<SchemaElementType> getAllowReferencesToUndefinedElementTypes()
571  {
572    return Collections.unmodifiableSet(allowReferencesToUndefinedElementTypes);
573  }
574
575
576
577  /**
578   * Specifies the types of schema elements that can be referenced by other
579   * elements without the referenced types being known to the schema validator
580   * (e.g., by having been previously defined in the schema files).
581   *
582   * @param  undefinedElementTypes
583   *              The types of schema elements that can be referenced by other
584   *              elements without the referenced types being known to the
585   *              schema validator.  It may be {@code null} or empty if no
586   *              undefined schema elements will be permitted.
587   */
588  public void setAllowReferencesToUndefinedElementTypes(
589       @Nullable final SchemaElementType... undefinedElementTypes)
590  {
591    setAllowReferencesToUndefinedElementTypes(
592         StaticUtils.toList(undefinedElementTypes));
593  }
594
595
596
597  /**
598   * Specifies the types of schema elements that can be referenced by other
599   * elements without the referenced types being known to the schema validator
600   * (e.g., by having been previously defined in the schema files).
601   *
602   * @param  undefinedElementTypes
603   *              The types of schema elements that can be referenced by other
604   *              elements without the referenced types being known to the
605   *              schema validator.  It may be {@code null} or empty if no
606   *              undefined schema elements will be permitted.
607   */
608  public void setAllowReferencesToUndefinedElementTypes(
609       @Nullable final Collection<SchemaElementType> undefinedElementTypes)
610  {
611    allowReferencesToUndefinedElementTypes.clear();
612    if (undefinedElementTypes != null)
613    {
614      allowReferencesToUndefinedElementTypes.addAll(undefinedElementTypes);
615    }
616  }
617
618
619
620  /**
621   * Indicates whether the same schema element may be defined multiple times.
622   * By default, each schema element may be defined only once.
623   *
624   * @return  {@code true} if the same schema element may be defined multiple
625   *          times, in which case subsequent definitions will override previous
626   *          definitions, or {@code false} if an error will be reported if the
627   *          same element is encountered multiple times.
628   */
629  public boolean allowRedefiningElements()
630  {
631    return allowRedefiningElements;
632  }
633
634
635
636  /**
637   * Specifies whether the same schema element may be defined multiple times.
638   *
639   * @param  allowRedefiningElements
640   *              Indicates whether the same schema element may be defined
641   *              multiple times.  If this is {@code true}, then a schema
642   *              element may be defined multiple times, with the most recent
643   *              definition ultimately being used, as long as the redefined
644   *              definition keeps the same OID and names of the former
645   *              definition (although the redefined element may add additional
646   *              names), but other details may be changed.  If this is
647   *              {@code false}, then any attempt to define the same element
648   *              multiple times will be reported as an error.
649   */
650  public void setAllowRedefiningElements(final boolean allowRedefiningElements)
651  {
652    this.allowRedefiningElements = allowRedefiningElements;
653  }
654
655
656
657  /**
658   * Indicates whether to allow schema elements that do not contain names but
659   * may only be identified by an OID (or by the rule ID in the case of DIT
660   * structure rules).  Note that this does not apply to attribute syntaxes,
661   * which cannot have names and may only be referenced by OID.  LDAP does allow
662   * schema elements without names, but such elements are not as user-friendly
663   * and it may not be desirable to have such definitions.  By default, the
664   * schema validator will allow elements without names.
665   *
666   * @return  {@code true} if the schema validator will elements without names,
667   *          or {@code false} if an error will be reported for each schema
668   *          element (other than attribute syntaxes) without a name.
669   */
670  public boolean allowElementsWithoutNames()
671  {
672    return allowElementsWithoutNames;
673  }
674
675
676
677  /**
678   * Specifies whether to allow schema elements that do not contain names but
679   * may only be identified by an OID (or by the rule ID in the case of DIT
680   * structure rules).  Note that this does not apply to attribute syntaxes,
681   * which cannot have names and may only be referenced by OID.  LDAP does allow
682   * schema elements without names, but such elements are not as user-friendly
683   * and it may not be desirable to have such definitions.
684   *
685   * @param  allowElementsWithoutNames
686   *              Indicates whether to allow schema elements that do not contain
687   *              names.  If this is {@code true}, then elements without names
688   *              will be allowed.  If this is {@code false}, then an error will
689   *              be reported for each element (other than attribute syntaxes)
690   *              that does not have a name.
691   */
692  public void setAllowElementsWithoutNames(
693                   final boolean allowElementsWithoutNames)
694  {
695    this.allowElementsWithoutNames = allowElementsWithoutNames;
696  }
697
698
699
700  /**
701   * Indicates whether schema elements will be permitted to include non-numeric
702   * object identifiers that are comprised of the name for that element with
703   * "-oid" appended to it.  For example, if an attribute is named "my-attr",
704   * then "my-attr-oid" may be allowed as an alternative to a numeric OID.
705   * While the official specification requires valid numeric OIDs, some servers
706   * are more relaxed about this requirement and allow OIDs to use the alternate
707   * form referenced above.  By default, valid numeric OIDs will be required.
708   *
709   * @return  {@code true} if non-numeric OIDs will be allowed if they are
710   *          comprised of the schema element name followed by "-oid", or
711   *          {@code false} if not.
712   */
713  public boolean allowNonNumericOIDsUsingName()
714  {
715    return allowNonNumericOIDsUsingName;
716  }
717
718
719
720  /**
721   * Indicates whether schema elements will be permitted to include non-numeric
722   * object identifiers that are of a form other than one of the element names
723   * followed by "-oid".  By default, valid numeric OIDs will be required.
724   *
725   * @return  {@code true} if non-numeric OIDs will be allowed if they are not
726   *          comprised of the schema element name followed by "-oid", or
727   *          {@code false} if not.
728   */
729  public boolean allowNonNumericOIDsNotUsingName()
730  {
731    return allowNonNumericOIDsNotUsingName;
732  }
733
734
735
736  /**
737   * Indicates whether to use strict validation for numeric object identifiers.
738   * If strict validation is to be used, then each OID must contain at least two
739   * components, the first component must be zero, one or, two, and the second
740   * component may only be greater than 39 if the first component is two.  By
741   * default, strict validation will be performed for numeric OIDs.
742   *
743   * @return  {@code true} if strict validation will be performed for numeric
744   *          OIDs, or {@code false} if more relaxed validation will be used
745   *          that only requires them to be comprised of a non-empty string
746   *          comprised only of digits and periods that does not start or end
747   *          with a period and does not contain consecutive periods.
748   */
749  public boolean useStrictOIDValidation()
750  {
751    return useStrictOIDValidation;
752  }
753
754
755
756  /**
757   * Specifies the behavior to use when validating object identifiers.
758   *
759   * @param  allowNonNumericOIDsUsingName
760   *              Indicates whether to allow non-numeric OIDs if they are
761   *              comprised of the name for the schema element followed by
762   *              "-oid".  If this is {@code true}, then non-numeric OIDs will
763   *              be allowed if they use that form.  If this is {@code false},
764   *              then an error will be reported for schema elements with a
765   *              non-numeric OID in that form.
766   * @param  allowNonNumericOIDsNotUsingName
767   *              Indicates whether to allow non-numeric OIDs if they are not
768   *              comprised of the name for the schema element followed by
769   *              "-oid".  If this is {@code true}, then non-numeric OIDs will
770   *              be allowed if they use that form.  If this is {@code false},
771   *              then an error will be reported for schema elements with a
772   *              non-numeric OID that does not use the element name.
773   * @param  useStrictOIDValidation
774   *              Indicates whether to use strict validation for numeric
775   *              object identifiers.  If this is {@code false}, then numeric
776   *              OIDs will be required to be comprised entirely of digits and
777   *              periods, must not start or end with a period, and must not
778   *              contain consecutive periods.  If this is {@code true}, then
779   *              numeric OIDs must also consist of at least two components,
780   *              the first component must be zero, one or two, and the second
781   *              component may only be greater than 39 if the first component
782   *              is two.
783   */
784  public void setOIDValidation(final boolean allowNonNumericOIDsUsingName,
785                               final boolean allowNonNumericOIDsNotUsingName,
786                               final boolean useStrictOIDValidation)
787  {
788    this.allowNonNumericOIDsUsingName = allowNonNumericOIDsUsingName;
789    this.allowNonNumericOIDsNotUsingName = allowNonNumericOIDsNotUsingName;
790    this.useStrictOIDValidation = useStrictOIDValidation;
791  }
792
793
794
795  /**
796   * Indicates whether to allow schema element names that start with a digit.
797   * LDAP specifications require that the first character of a schema element
798   * name be a letter, but some servers allow names that start with digits.  By
799   * default, schema element names will not be allowed to start with a digit.
800   *
801   * @return  {@code true} if schema element names will be permitted to start
802   *          with digits, or {@code false} if an error will be reported for
803   *          names that start with a digit.
804   */
805  public boolean allowNamesWithInitialDigit()
806  {
807    return allowNamesWithInitialDigit;
808  }
809
810
811
812  /**
813   * Specifies whether to allow schema element names that start with a digit.
814   * LDAP specifications require that the first character of a schema element
815   * name be a letter, but some servers allow names that start with digits.
816   *
817   * @param  allowNamesWithInitialDigit
818   *              Indicates whether to allow schema element names that start
819   *              with a digit.  If this is {@code true}, then names will be
820   *              permitted to start with a digit.  If this is {@code false},
821   *              then an error will be reported for each name that starts with
822   *              a digit.
823   */
824  public void setAllowNamesWithInitialDigit(
825                   final boolean allowNamesWithInitialDigit)
826  {
827    this.allowNamesWithInitialDigit = allowNamesWithInitialDigit;
828  }
829
830
831
832  /**
833   * Indicates whether to allow schema element names that start with a hyphen.
834   * LDAP specifications require that the first character of a schema element
835   * name be a letter, but some servers allow names that start with hyphens.  By
836   * default, schema element names will not be allowed to start with a hyphen.
837   *
838   * @return  {@code true} if schema element names will be permitted to start
839   *          with hyphens, or {@code false} if an error will be reported for
840   *          names that start with a hyphen.
841   */
842  public boolean allowNamesWithInitialHyphen()
843  {
844    return allowNamesWithInitialHyphen;
845  }
846
847
848
849  /**
850   * Specifies whether to allow schema element names that start with a hyphen.
851   * LDAP specifications require that the first character of a schema element
852   * name be a letter, but some servers allow names that start with hyphens.
853   *
854   * @param  allowNamesWithInitialHyphen
855   *              Indicates whether to allow schema element names that start
856   *              with a hyphen.  If this is {@code true}, then names will be
857   *              permitted to start with a hyphen.  If this is {@code false},
858   *              then an error will be reported for each name that starts with
859   *              a hyphen.
860   */
861  public void setAllowNamesWithInitialHyphen(
862                   final boolean allowNamesWithInitialHyphen)
863  {
864    this.allowNamesWithInitialHyphen = allowNamesWithInitialHyphen;
865  }
866
867
868
869  /**
870   * Indicates whether to allow schema element names that contain the underscore
871   * character.  LDAP specifications do not permit underscores in schema element
872   * names, but some servers do allow it.  By default, schema element names will
873   * not be allowed to contain an underscore.
874   *
875   * @return  {@code true} if schema element names will be permitted to contain
876   *          underscores, or {@code false} if an error will be reported for
877   *          names that contain an underscore.
878   */
879  public boolean allowNamesWithUnderscore()
880  {
881    return allowNamesWithUnderscore;
882  }
883
884
885
886  /**
887   * Indicates whether to allow schema element names that contain the underscore
888   * character.  LDAP specifications do not permit underscores in schema element
889   * names, but some servers do allow it.
890   *
891   * @param  allowNamesWithUnderscore
892   *              Indicates whether to allow schema element names that contain
893   *              the underscore character.  If this is {@code true}, then names
894   *              will be permitted to contain underscores.  If this is
895   *              {@code false}, then an error will be reported for each name
896   *              that contains an underscore.
897   */
898  public void setAllowNamesWithUnderscore(
899                   final boolean allowNamesWithUnderscore)
900  {
901    this.allowNamesWithUnderscore = allowNamesWithUnderscore;
902  }
903
904
905
906  /**
907   * Indicates whether to allow schema elements to have empty descriptions.
908   * LDAP specifications forbid empty quoted strings in schema definitions, but
909   * some servers allow it.  By default, empty descriptions will not be allowed,
910   * and an error will be reported for every schema element that has an empty
911   * description.
912   *
913   * @return  {@code true} if empty descriptions will be allowed, or
914   *          {@code false} if errors will be reported for schema elements with
915   *          empty descriptions.
916   */
917  public boolean allowEmptyDescription()
918  {
919    return allowEmptyDescription;
920  }
921
922
923
924  /**
925   * Specifies whether to allow schema elements to have empty descriptions.
926   * LDAP specifications forbid empty quoted strings in schema definitions, but
927   * some servers allow it.
928   *
929   * @param  allowEmptyDescription
930   *              Indicates whether to allow schema elements to have empty
931   *              descriptions.  If this is {@code true}, then schema elements
932   *              will be permitted to have empty descriptions.  If it is
933   *              {@code false}, then an error will be reported for each
934   *              schema element with an empty description.
935   */
936  public void setAllowEmptyDescription(final boolean allowEmptyDescription)
937  {
938    this.allowEmptyDescription = allowEmptyDescription;
939  }
940
941
942
943  /**
944   * Retrieves a list of the attribute syntaxes that will be used in the course
945   * of validating attribute type definitions.  By default, the schema validator
946   * will be preconfigured with a default set of standard attribute syntaxes
947   * (as set by the {@link #configureLDAPSDKDefaultAttributeSyntaxes} method),
948   * in addition to any attribute type definitions contained in schema entries.
949   *
950   * @return  A list of the attribute syntaxes that will be used in the course
951   *          of validating attribute type definitions, or an empty list if the
952   *          list of available syntaxes will be defined in the schema files.
953   */
954  @NotNull()
955  public List<AttributeSyntaxDefinition> getAttributeSyntaxes()
956  {
957    return Collections.unmodifiableList(new ArrayList<>(attributeSyntaxList));
958  }
959
960
961
962  /**
963   * Specifies a set of attribute syntaxes that will be used in the course
964   * of validating attribute type definitions.
965   *
966   * @param  attributeSyntaxes
967   *              The set of attribute syntaxes that will be used in the course
968   *              of validating attribute type definitions.  It may be
969   *              {@code null} or empty if only syntaxes defined in the schema
970   *              files will be used.
971   */
972  public void setAttributeSyntaxes(
973       @Nullable final Collection<AttributeSyntaxDefinition> attributeSyntaxes)
974  {
975    attributeSyntaxList = new ArrayList<>();
976    attributeSyntaxMap = new HashMap<>();
977
978    if (attributeSyntaxes != null)
979    {
980      for (final AttributeSyntaxDefinition d : attributeSyntaxes)
981      {
982        attributeSyntaxList.add(d);
983        attributeSyntaxMap.put(StaticUtils.toLowerCase(d.getOID()), d);
984      }
985    }
986  }
987
988
989
990  /**
991   * Configures the schema validator to use a default set of attribute syntaxes
992   * that are known to the LDAP SDK.  Any other syntaxes that may have been
993   * defined will be cleared.
994   */
995  public void configureLDAPSDKDefaultAttributeSyntaxes()
996  {
997    try
998    {
999      final Set<AttributeSyntaxDefinition> defaultSyntaxes =
1000           new LinkedHashSet<>();
1001      final Schema schema = Schema.getDefaultStandardSchema();
1002      defaultSyntaxes.addAll(schema.getAttributeSyntaxes());
1003
1004      if (PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE)
1005      {
1006        defaultSyntaxes.add(new AttributeSyntaxDefinition(
1007             "( 1.3.6.1.4.1.30221.1.3.1 DESC 'User Password Syntax' )"));
1008        defaultSyntaxes.add(new AttributeSyntaxDefinition(
1009             "( 1.3.6.1.4.1.30221.1.3.2 " +
1010                  "DESC 'Relative Subtree Specification' )"));
1011        defaultSyntaxes.add(new AttributeSyntaxDefinition(
1012             "( 1.3.6.1.4.1.30221.1.3.3 " +
1013                  "DESC 'Absolute Subtree Specification' )"));
1014        defaultSyntaxes.add(new AttributeSyntaxDefinition(
1015             "( 1.3.6.1.4.1.30221.1.3.4 " +
1016                  "DESC 'Sun-defined Access Control Information' )"));
1017        defaultSyntaxes.add(new AttributeSyntaxDefinition(
1018             "( 1.3.6.1.4.1.30221.2.3.1 DESC 'Compact Timestamp' )"));
1019        defaultSyntaxes.add(new AttributeSyntaxDefinition(
1020             "( 1.3.6.1.4.1.30221.2.3.2 DESC 'LDAP URL' )"));
1021        defaultSyntaxes.add(new AttributeSyntaxDefinition(
1022             "( 1.3.6.1.4.1.30221.2.3.3 DESC 'Hex String' )"));
1023        defaultSyntaxes.add(new AttributeSyntaxDefinition(
1024             "( 1.3.6.1.4.1.30221.2.3.4 DESC 'JSON Object' )"));
1025      }
1026
1027      setAttributeSyntaxes(defaultSyntaxes);
1028    }
1029    catch (final Exception e)
1030    {
1031      // This should never happen.
1032      Debug.debugException(e);
1033    }
1034  }
1035
1036
1037
1038  /**
1039   * Indicates whether to allow attribute type definitions to be missing an
1040   * attribute syntax definition, by neither directly specifying the attribute
1041   * syntax OID nor referencing a superior attribute type from which the syntax
1042   * will be inherited.  The LDAP specification requires that each attribute
1043   * type specify its syntax or inherit it from a superior type, but some
1044   * directory servers will assume a default syntax (e.g., Directory String) for
1045   * attribute types that do not specify it and are not configured with a
1046   * superior type.  By default, any attribute type that does not specify a
1047   * syntax and cannot inherit it from a superior type will be flagged as an
1048   * error.
1049   *
1050   * @return  {@code true} if attribute type definitions will be permitted to
1051   *          omit both an attribute syntax and a superior type, or
1052   *          {@code false} if an error will be reported for each such attribute
1053   *          type.
1054   */
1055  public boolean allowAttributeTypesWithoutSyntax()
1056  {
1057    return allowAttributeTypesWithoutSyntax;
1058  }
1059
1060
1061
1062  /**
1063   * Specifies whether to allow attribute type definitions to be missing an
1064   * attribute syntax definition, by neither directly specifying the attribute
1065   * syntax OID nor referencing a superior attribute type from which the syntax
1066   * will be inherited.  The LDAP specification requires that each attribute
1067   * type specify its syntax or inherit it from a superior type, but some
1068   * directory servers will assume a default syntax (e.g., Directory String) for
1069   * attribute types that do not specify it and are not configured with a
1070   * superior type.
1071   *
1072   * @param  allowAttributeTypesWithoutSyntax
1073   *              Indicates whether to allow attribute type definitions to be
1074   *              missing an attribute syntax definition, by neither directly
1075   *              specifying the attribute syntax OID nor referencing a superior
1076   *              attribute type from which the syntax will be inherited.  If
1077   *              this is {@code true}, then attribute types that do not specify
1078   *              either a syntax OID or a superior type will be permitted.  If
1079   *              this is {@code false}, then an error will be reported for
1080   *              each such attribute type.
1081   */
1082  public void setAllowAttributeTypesWithoutSyntax(
1083                   final boolean allowAttributeTypesWithoutSyntax)
1084  {
1085    this.allowAttributeTypesWithoutSyntax = allowAttributeTypesWithoutSyntax;
1086  }
1087
1088
1089
1090  /**
1091   * Retrieves a list of the matching rules that will be used in the course of
1092   * of validating attribute type definitions.  By default, the schema validator
1093   * will be preconfigured with a default set of standard matching rules (as set
1094   * by the {@link #configureLDAPSDKDefaultMatchingRules()} method), in addition
1095   * to any matching rule definitions contained in schema entries.
1096   *
1097   * @return  A list of the matching rules  that will be used in the course of
1098   *          validating attribute type definitions, or an empty list if the
1099   *          list of matching rules will be defined in the schema files.
1100   */
1101  @NotNull()
1102  public List<MatchingRuleDefinition> getMatchingRuleDefinitions()
1103  {
1104    return Collections.unmodifiableList(new ArrayList<>(matchingRuleList));
1105  }
1106
1107
1108
1109  /**
1110   * Specifies a set of matching rules that will be used in the course of
1111   * validating attribute type definitions.
1112   *
1113   * @param  matchingRules
1114   *              The set of attribute syntaxes that will be used in the course
1115   *              of validating attribute type definitions.  It may be
1116   *              {@code null} or empty if only syntaxes defined in the schema
1117   *              files will be used.
1118   */
1119  public void setMatchingRules(
1120       @Nullable final Collection<MatchingRuleDefinition> matchingRules)
1121  {
1122    matchingRuleList = new ArrayList<>();
1123    matchingRuleMap = new HashMap<>();
1124
1125    if (matchingRules != null)
1126    {
1127      for (final MatchingRuleDefinition d : matchingRules)
1128      {
1129        matchingRuleList.add(d);
1130        matchingRuleMap.put(StaticUtils.toLowerCase(d.getOID()), d);
1131        for (final String name : d.getNames())
1132        {
1133          matchingRuleMap.put(StaticUtils.toLowerCase(name), d);
1134        }
1135      }
1136    }
1137  }
1138
1139
1140
1141  /**
1142   * Configures the schema validator to use a default set of matching rules that
1143   * that are known to the LDAP SDK.  Any other syntaxes that may have been
1144   * defined will be cleared.
1145   */
1146  public void configureLDAPSDKDefaultMatchingRules()
1147  {
1148    try
1149    {
1150      final Set<MatchingRuleDefinition> defaultMatchingRules =
1151           new LinkedHashSet<>();
1152      final Schema schema = Schema.getDefaultStandardSchema();
1153      defaultMatchingRules.addAll(schema.getMatchingRules());
1154
1155      if (PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE)
1156      {
1157        defaultMatchingRules.add(new MatchingRuleDefinition(
1158             "( 1.3.6.1.4.1.30221.1.4.1 NAME 'ds-mr-double-metaphone-approx' " +
1159                  "DESC 'Double Metaphone Approximate Match' " +
1160                  "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )"));
1161        defaultMatchingRules.add(new MatchingRuleDefinition(
1162             "( 1.3.6.1.4.1.30221.1.4.2 NAME 'ds-mr-user-password-exact' " +
1163                  "DESC 'user password exact matching rule' " +
1164                  "SYNTAX 1.3.6.1.4.1.30221.1.3.1 )"));
1165        defaultMatchingRules.add(new MatchingRuleDefinition(
1166             "( 1.3.6.1.4.1.30221.1.4.3 NAME 'ds-mr-user-password-equality' " +
1167                  "DESC 'user password matching rule' " +
1168                  "SYNTAX 1.3.6.1.4.1.30221.1.3.1 )"));
1169        defaultMatchingRules.add(new MatchingRuleDefinition(
1170             "( 1.3.6.1.4.1.30221.1.4.4 NAME 'historicalCsnOrderingMatch' " +
1171                  "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )"));
1172        defaultMatchingRules.add(new MatchingRuleDefinition(
1173             "( 1.3.6.1.4.1.30221.1.4.902 NAME 'caseExactIA5SubstringsMatch' " +
1174                  "SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )"));
1175        defaultMatchingRules.add(new MatchingRuleDefinition(
1176             "( 1.3.6.1.4.1.30221.2.4.1 NAME 'compactTimestampMatch' " +
1177                  "SYNTAX 1.3.6.1.4.1.30221.2.3.1 )"));
1178        defaultMatchingRules.add(new MatchingRuleDefinition(
1179             "( 1.3.6.1.4.1.30221.2.4.2 NAME 'compactTimestampOrderingMatch' " +
1180                  "SYNTAX 1.3.6.1.4.1.30221.2.3.1 )"));
1181        defaultMatchingRules.add(new MatchingRuleDefinition(
1182             "( 1.3.6.1.4.1.30221.2.4.3 NAME 'ldapURLMatch' " +
1183                  "SYNTAX 1.3.6.1.4.1.30221.2.3.2 )"));
1184        defaultMatchingRules.add(new MatchingRuleDefinition(
1185             "( 1.3.6.1.4.1.30221.2.4.4 NAME 'hexStringMatch' " +
1186                  "SYNTAX 1.3.6.1.4.1.30221.2.3.3 )"));
1187        defaultMatchingRules.add(new MatchingRuleDefinition(
1188             "( 1.3.6.1.4.1.30221.2.4.5 NAME 'hexStringOrderingMatch' " +
1189                  "SYNTAX 1.3.6.1.4.1.30221.2.3.3 )"));
1190        defaultMatchingRules.add(new MatchingRuleDefinition(
1191             "( 1.3.6.1.4.1.30221.2.4.12 NAME 'jsonObjectExactMatch' " +
1192                  "SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
1193        defaultMatchingRules.add(new MatchingRuleDefinition(
1194             "( 1.3.6.1.4.1.30221.2.4.13 " +
1195                  "NAME 'jsonObjectFilterExtensibleMatch' " +
1196                  "SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
1197        defaultMatchingRules.add(new MatchingRuleDefinition(
1198             "( 1.3.6.1.4.1.30221.2.4.14 NAME 'relativeTimeExtensibleMatch' " +
1199                  "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )"));
1200        defaultMatchingRules.add(new MatchingRuleDefinition(
1201             "( 1.3.6.1.4.1.30221.2.4.15 " +
1202                  "NAME 'jsonObjectCaseSensitiveNamesCaseSensitiveValues' " +
1203                  "SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
1204        defaultMatchingRules.add(new MatchingRuleDefinition(
1205             "( 1.3.6.1.4.1.30221.2.4.16 " +
1206                  "NAME 'jsonObjectCaseInsensitiveNamesCaseSensitiveValues' " +
1207                  "SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
1208        defaultMatchingRules.add(new MatchingRuleDefinition(
1209             "( 1.3.6.1.4.1.30221.2.4.17 NAME " +
1210                  "'jsonObjectCaseInsensitiveNamesCaseInsensitiveValues' " +
1211                  "SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
1212      }
1213
1214      setMatchingRules(defaultMatchingRules);
1215    }
1216    catch (final Exception e)
1217    {
1218      // This should never happen.
1219      Debug.debugException(e);
1220    }
1221  }
1222
1223
1224
1225  /**
1226   * Indicates whether to allow attribute type definitions to be missing an
1227   * equality matching definition, by neither directly specifying the matching
1228   * rule name or OID nor referencing a superior attribute type from which the
1229   * matching rule will be inherited.  It is technically legal to have an
1230   * attribute type definition that does not include an equality matching rule
1231   * and does not inherit an equality matching rule from a superior type, this
1232   * may not be desirable, as the server should fall back to byte-for-byte
1233   * matching (although some servers may assume a default matching rule based on
1234   * the syntax).  By default, attribute types that do not specify an equality
1235   * matching rule will be permitted.
1236   *
1237   * @return  {@code true} if attribute type definitions will be permitted to
1238   *          omit both an attribute syntax and a superior type, or
1239   *          {@code false} if an error will be reported for each such attribute
1240   *          type.
1241   */
1242  public boolean allowAttributeTypesWithoutEqualityMatchingRule()
1243  {
1244    return allowAttributeTypesWithoutEqualityMatchingRule;
1245  }
1246
1247
1248
1249  /**
1250   * Indicates whether to allow attribute type definitions to be missing an
1251   * equality matching definition, by neither directly specifying the matching
1252   * rule name or OID nor referencing a superior attribute type from which the
1253   * matching rule will be inherited.  It is technically legal to have an
1254   * attribute type definition that does not include an equality matching rule
1255   * and does not inherit an equality matching rule from a superior type, this
1256   * may not be desirable, as the server should fall back to byte-for-byte
1257   * matching (although some servers may assume a default matching rule based on
1258   * the syntax).
1259   *
1260   * @param  allowAttributeTypesWithoutEqualityMatchingRule
1261   *              Specifies whether to allow attribute type definitions to be
1262   *              missing an equality matching definition, by neither directly
1263   *              specifying the matching rule name or OID nor referencing a
1264   *              superior attribute type from which the matching rule will be
1265   *              inherited.  If this is {@code true}, then attribute types that
1266   *              do not specify either an equality matching rule or a superior
1267   *              type will be permitted.  If this is {@code false}, then an
1268   *              error will be reported for each such attribute type.
1269   */
1270  public void setAllowAttributeTypesWithoutEqualityMatchingRule(
1271                   final boolean allowAttributeTypesWithoutEqualityMatchingRule)
1272  {
1273    this.allowAttributeTypesWithoutEqualityMatchingRule =
1274         allowAttributeTypesWithoutEqualityMatchingRule;
1275  }
1276
1277
1278
1279  /**
1280   * Indicates whether to allow object classes with multiple superior classes.
1281   * This is allowed by LDAP specifications, but some servers do not support it.
1282   * By default, object classes with multiple superior classes will be
1283   * permitted.
1284   *
1285   * @return  {@code true} if object classes will be allowed to have multiple
1286   *          superior classes, or {@code false} if an error will be reported
1287   *          for each object class with multiple superiors.
1288   */
1289  public boolean allowMultipleSuperiorObjectClasses()
1290  {
1291    return allowMultipleSuperiorObjectClasses;
1292  }
1293
1294
1295
1296  /**
1297   * Specifies whether to allow object classes with multiple superior classes.
1298   * This is allowed by LDAP specifications, but some servers do not support it.
1299   *
1300   * @param  allowMultipleSuperiorObjectClasses
1301   *              Indicates whether to allow object classes with multiple
1302   *              superior classes.  If this is {@code true}, then object
1303   *              classes with multiple superiors will be allowed.  If this is
1304   *              {@code false}, then an error will be reported for each
1305   *              object class with more than one superior class.
1306   */
1307  public void setAllowMultipleSuperiorObjectClasses(
1308                   final boolean allowMultipleSuperiorObjectClasses)
1309  {
1310    this.allowMultipleSuperiorObjectClasses =
1311         allowMultipleSuperiorObjectClasses;
1312  }
1313
1314
1315
1316  /**
1317   * Indicates whether to allow structural object classes that do not declare a
1318   * superior class.  Technically, structural object classes must inherit
1319   * from structural or abstract classes, although some servers may assume a
1320   * default superior class of "top" for a structural class that does not
1321   * declare any superiors.  By default, an error will be reported for each
1322   * structural object class that does not explicitly declare any superior
1323   * class.
1324   *
1325   * @return  {@code true} if object classes that do not declare their superiors
1326   *          will be permitted, ro {@code false} if an error will be reported
1327   *          for each structural class that does not declare any superiors.
1328   */
1329  public boolean allowStructuralObjectClassWithoutSuperior()
1330  {
1331    return allowStructuralObjectClassWithoutSuperior;
1332  }
1333
1334
1335
1336  /**
1337   * Specifies whether to allow structural object classes that do not declare a
1338   * superior class.  Technically, structural object classes must inherit
1339   * from structural or abstract classes, although some servers may assume a
1340   * default superior class of "top" for a structural class that does not
1341   * declare any superiors.
1342   *
1343   * @param  allowStructuralObjectClassWithoutSuperior
1344   *              Indicates whether to allow structural object classes that do
1345   *              not declare a superior class.  If this is {@code true}, then
1346   *              structural object classes that do not declare any superior
1347   *              class will be assumed to subclass "top".  if this is
1348   *              {@code false}, then an error will be reported for each
1349   *              structural object class that does not define any superior
1350   *              class.
1351   */
1352  public void setAllowStructuralObjectClassWithoutSuperior(
1353                   final boolean allowStructuralObjectClassWithoutSuperior)
1354  {
1355    this.allowStructuralObjectClassWithoutSuperior =
1356         allowStructuralObjectClassWithoutSuperior;
1357  }
1358
1359
1360
1361  /**
1362   * Indicates whether to allow object classes with an invalid inheritance
1363   * relationship.  As per LDAP specifications, structural object classes can
1364   * only inherit from structural or abstract classes, auxiliary classes can
1365   * only inherit from auxiliary or abstract classes, and abstract classes can
1366   * only inherit from other abstract classes.  By default, the schema validator
1367   * will report an error for any object class that violates this constraint.
1368   *
1369   * @return  {@code true} if the schema validator will allow object classes
1370   *          with invalid inheritance relationships, or {@code false} if an
1371   *          error will be reported for each object class with an invalid
1372   *          superior class.
1373   */
1374  public boolean allowInvalidObjectClassInheritance()
1375  {
1376    return allowInvalidObjectClassInheritance;
1377  }
1378
1379
1380
1381  /**
1382   * Specifies whether to allow object classes with an invalid inheritance
1383   * relationship.  As per LDAP specifications, structural object classes can
1384   * only inherit from structural or abstract classes, auxiliary classes can
1385   * only inherit from auxiliary or abstract classes, and abstract classes can
1386   * only inherit from other abstract classes.
1387   *
1388   * @param  allowInvalidObjectClassInheritance
1389   *              Indicates whether to allow object classes with an invalid
1390   *              inheritance relationship.  If this is {@code true}, then
1391   *              invalid inheritance relationships will be allowed.  If this is
1392   *              {@code false}, then an error will be reported for each
1393   *              object class with an invalid superior class reference.
1394   */
1395  public void setAllowInvalidObjectClassInheritance(
1396                   final boolean allowInvalidObjectClassInheritance)
1397  {
1398    this.allowInvalidObjectClassInheritance =
1399         allowInvalidObjectClassInheritance;
1400  }
1401
1402
1403
1404  /**
1405   * Indicates whether to allow collective attribute type definitions.
1406   * Collective attributes (as described in RFC 3671) have read-only values that
1407   * are generated by the server rather than provided by clients.  Although they
1408   * are part of the LDAP specification, some servers do not support them or
1409   * provide alternate virtual attribute mechanisms.  By default, collective
1410   * attribute definitions will be allowed.
1411   *
1412   * @return  {@code true} if collective attributes will be allowed, or
1413   *          {@code false} if the schema validator will report an error for
1414   *          each collective attribute type definition.
1415   */
1416  public boolean allowCollectiveAttributes()
1417  {
1418    return allowCollectiveAttributes;
1419  }
1420
1421
1422
1423  /**
1424   * Specifies whether to allow collective attribute type definitions.
1425   * Collective attributes (as described in RFC 3671) have read-only values that
1426   * are generated by the server rather than provided by clients.  Although they
1427   * are part of the LDAP specification, some servers do not support them or
1428   * provide alternate virtual attribute mechanisms.
1429   *
1430   * @param  allowCollectiveAttributes
1431   *              Indicates whether to allow collective attribute type
1432   *              definitions.  If this is {@code true}, then collective
1433   *              attribute type definitions will be allowed.  If this is
1434   *              {@code false}, then an error will be reported for each
1435   *              collective attribute type definition.
1436   */
1437  public void setAllowCollectiveAttributes(
1438                   final boolean allowCollectiveAttributes)
1439  {
1440    this.allowCollectiveAttributes = allowCollectiveAttributes;
1441  }
1442
1443
1444
1445  /**
1446   * Indicates whether to allow schema elements declared with the OBSOLETE
1447   * modifier.  Obsolete schema elements are those that are no longer active
1448   * and cannot be used in updates, although some servers may not support
1449   * obsolete schema elements.  By default, obsolete elements will be allowed.
1450   *
1451   * @return  {@code true} if schema elements declared with the OBSOLETE
1452   *          modifier will be allowed, or {@code false} if an error will be
1453   *          reported for each schema element declared as OBSOLETE.
1454   */
1455  public boolean allowObsoleteElements()
1456  {
1457    return allowObsoleteElements;
1458  }
1459
1460
1461
1462  /**
1463   * Specifies whether to allow schema elements declared with the OBSOLETE
1464   * modifier.  Obsolete schema elements are those that are no longer active
1465   * and cannot be used in updates, although some servers may not support
1466   * obsolete schema elements.
1467   *
1468   * @param  allowObsoleteElements
1469   *              Indicates whether to allow schema elements declared with the
1470   *              OBSOLETE modifier.  If this is {@code true}, then obsolete
1471   *              elements will be allowed.  If this is {@code false}, then
1472   *              an error will be reported for each OBSOLETE schema element.
1473   */
1474  public void setAllowObsoleteElements(final boolean allowObsoleteElements)
1475  {
1476    this.allowObsoleteElements = allowObsoleteElements;
1477  }
1478
1479
1480
1481  /**
1482   * Validates the schema definitions in the file or set of files at the given
1483   * path.
1484   *
1485   * @param  schemaPath
1486   *              The file or directory containing the schema definitions to
1487   *              validate.  It must not be {@code null}, and the target file
1488   *              or directory must exist.  If it is a directory, then files in
1489   *              the directory will be processed in lexicographic order by
1490   *              filename, optionally restricted to files matching the schema
1491   *              file name pattern.
1492   * @param  existingSchema
1493   *              An existing schema to use in the course of validating
1494   *              definitions.  It may be {@code null} if there is no existing
1495   *              schema and only the definitions read from the provided path
1496   *              should be used.
1497   * @param  errorMessages
1498   *              A list that will be updated with error messages about any
1499   *              problems identified during processing.  It must not be
1500   *              {@code null}, and it must be updatable.
1501   *
1502   * @return  A {@code Schema} object that contains the definitions that were
1503   *          loaded.  This may include schema elements that were flagged as
1504   *          invalid (if they could be parsed).  If an existing schema was
1505   *          already available, the schema that is returned will be a merged
1506   *          representation of the existing schema and the newly loaded schema.
1507   *          This may be {@code null} if an error prevented any schema files
1508   *          from being processed and no existing schema was provided.
1509   */
1510  @Nullable()
1511  public Schema validateSchema(@NotNull final File schemaPath,
1512                               @Nullable final Schema existingSchema,
1513                               @NotNull final List<String> errorMessages)
1514  {
1515    final boolean originalAllowEmptyDescription =
1516         SchemaElement.allowEmptyDescription();
1517
1518    try
1519    {
1520      SchemaElement.setAllowEmptyDescription(true);
1521
1522      final int originalErrorMessagesSize = errorMessages.size();
1523      final AtomicInteger schemaFilesProcessed = new AtomicInteger(0);
1524      final List<File> nonSchemaFilesIgnored = new ArrayList<>();
1525      final Schema schema = validateSchema(schemaPath, errorMessages,
1526           existingSchema, schemaFilesProcessed, nonSchemaFilesIgnored);
1527
1528      // If no error messages were written, and if no schema files were
1529      // processed, then add an error message to indicate that.
1530      if ((schemaFilesProcessed.get() == 0) &&
1531           (errorMessages.size() == originalErrorMessagesSize))
1532      {
1533        switch (nonSchemaFilesIgnored.size())
1534        {
1535          case 0:
1536            errorMessages.add(
1537                 ERR_SCHEMA_VALIDATOR_NO_SCHEMA_FILES_NONE_IGNORED.get(
1538                      schemaPath.getAbsolutePath()));
1539            break;
1540
1541          case 1:
1542            errorMessages.add(
1543                 ERR_SCHEMA_VALIDATOR_NO_SCHEMA_FILES_ONE_IGNORED.get(
1544                      schemaPath.getAbsolutePath(),
1545                      nonSchemaFilesIgnored.get(0).getAbsolutePath()));
1546            break;
1547
1548          default:
1549            final StringBuilder buffer = new StringBuilder();
1550            final Iterator<File> fileIterator =
1551                 nonSchemaFilesIgnored.iterator();
1552            while (fileIterator.hasNext())
1553            {
1554              buffer.append('\'');
1555              buffer.append(fileIterator.next().getAbsolutePath());
1556              buffer.append('\'');
1557
1558              if (fileIterator.hasNext())
1559              {
1560                buffer.append(", ");
1561              }
1562            }
1563
1564            errorMessages.add(
1565                 ERR_SCHEMA_VALIDATOR_NO_SCHEMA_FILES_MULTIPLE_IGNORED.get(
1566                      schemaPath.getAbsolutePath(), buffer.toString()));
1567            break;
1568        }
1569      }
1570
1571      return schema;
1572    }
1573    finally
1574    {
1575      SchemaElement.setAllowEmptyDescription(originalAllowEmptyDescription);
1576    }
1577  }
1578
1579
1580
1581  /**
1582   * Validates the schema definitions in the file or set of files at the given
1583   * path.
1584   *
1585   * @param  schemaPath
1586   *              The file or directory containing the schema definitions to
1587   *              validate.  It must not be {@code null}, and the target file
1588   *              or directory must exist.  If it is a directory, then files in
1589   *              the directory will be processed in lexicographic order by
1590   *              filename, optionally restricted to files matching the schema
1591   *              file name pattern.
1592   * @param  errorMessages
1593   *              A list that will be updated with error messages about any
1594   *              problems identified during processing.  It must not be
1595   *              {@code null}, and it must be updatable.
1596   * @param  existingSchema
1597   *              The existing schema to use in the course of validating
1598   *              definitions.  It may be {@code null} if there is no
1599   *              existing schema and only the definitions read from the
1600   *              provided path should be used.
1601   * @param  schemaFilesProcessed
1602   *              A counter that will be incremented for each schema file that
1603   *              is processed.
1604   * @param  nonSchemaFilesIgnored
1605   *              A list that should be updated with any files that are ignored
1606   *              because they do not match the configured schema file name
1607   *              pattern.
1608   *
1609   * @return  A {@code Schema} object that contains the definitions that were
1610   *          loaded.  If an existing schema was already available, the schema
1611   *          that is returned will be a merged representation of the existing
1612   *          schema and the newly loaded schema.  This may be {@code null} if
1613   *          an error prevented any schema files from being processed and no
1614   *          existing schema was provided.
1615   */
1616  @Nullable()
1617  private Schema validateSchema(@NotNull final File schemaPath,
1618                      @NotNull final List<String> errorMessages,
1619                      @Nullable final Schema existingSchema,
1620                      @NotNull final AtomicInteger schemaFilesProcessed,
1621                      @NotNull final List<File> nonSchemaFilesIgnored)
1622  {
1623    // Make sure the schema path represents a file or directory that exists.
1624    if (! schemaPath.exists())
1625    {
1626      errorMessages.add(ERR_SCHEMA_VALIDATOR_NO_SUCH_PATH.get(
1627           schemaPath.getAbsolutePath()));
1628      return existingSchema;
1629    }
1630    else if (schemaPath.isDirectory())
1631    {
1632      return validateSchemaDirectory(schemaPath, errorMessages, existingSchema,
1633           schemaFilesProcessed, nonSchemaFilesIgnored);
1634    }
1635    else
1636    {
1637      return validateSchemaFile(schemaPath, errorMessages, existingSchema,
1638           schemaFilesProcessed, nonSchemaFilesIgnored);
1639    }
1640  }
1641
1642
1643
1644  /**
1645   * Identifies and validates all schema files in the provided directory.
1646   *
1647   * @param  schemaDirectory
1648   *              The directory containing the schema files to examine.  It must
1649   *              must not be {@code null}, it must exist, and it must be a
1650   *              directory.
1651   * @param  errorMessages
1652   *              A list that will be updated with error messages about any
1653   *              problems identified during processing.  It must not be
1654   *              {@code null}, and it must be updatable.
1655   * @param  existingSchema
1656   *              The existing schema to use in the course of validating
1657   *              definitions.  It may be {@code null} if there is no
1658   *              existing schema and only the definitions read from the
1659   *              provided path should be used.
1660   * @param  schemaFilesProcessed
1661   *              A counter that will be incremented for each schema file that
1662   *              is processed.
1663   * @param  nonSchemaFilesIgnored
1664   *              A list that should be updated with any files that are ignored
1665   *              because they do not match the configured schema file name
1666   *              pattern.
1667   *
1668   * @return  A {@code Schema} object that contains the definitions that were
1669   *          loaded.  If an existing schema was already available, the schema
1670   *          that is returned will be a merged representation of the existing
1671   *          schema and the newly loaded schema.  This may be {@code null} if
1672   *          an error prevented any schema files from being processed and no
1673   *          existing schema was provided.
1674   */
1675  @Nullable()
1676  private Schema validateSchemaDirectory(@NotNull final File schemaDirectory,
1677                      @NotNull final List<String> errorMessages,
1678                      @Nullable final Schema existingSchema,
1679                      @NotNull final AtomicInteger schemaFilesProcessed,
1680                      @NotNull final List<File> nonSchemaFilesIgnored)
1681  {
1682    final TreeMap<String,File> schemaFiles = new TreeMap<>();
1683    final TreeMap<String,File> subDirectories = new TreeMap<>();
1684
1685    for (final File f : schemaDirectory.listFiles())
1686    {
1687      final String name = f.getName();
1688      if (f.isFile())
1689      {
1690        schemaFiles.put(name, f);
1691      }
1692      else
1693      {
1694        if (allowSchemaFilesInSubDirectories)
1695        {
1696          subDirectories.put(name, f);
1697        }
1698        else
1699        {
1700          errorMessages.add(ERR_SCHEMA_VALIDATOR_DIR_CONTAINS_SUBDIR.get(
1701               schemaDirectory.getAbsolutePath(), name));
1702        }
1703      }
1704    }
1705
1706
1707    Schema schema = existingSchema;
1708    for (final File f : schemaFiles.values())
1709    {
1710      final Schema newSchema = validateSchemaFile(f, errorMessages,
1711           schema, schemaFilesProcessed, nonSchemaFilesIgnored);
1712      if (schema == null)
1713      {
1714        schema = newSchema;
1715      }
1716      else
1717      {
1718        schema = Schema.mergeSchemas(schema, newSchema);
1719      }
1720    }
1721
1722    for (final File f : subDirectories.values())
1723    {
1724      final Schema newSchema =
1725           validateSchemaDirectory(f, errorMessages, schema,
1726                schemaFilesProcessed, nonSchemaFilesIgnored);
1727      if (schema == null)
1728      {
1729        schema = newSchema;
1730      }
1731      else
1732      {
1733        schema = Schema.mergeSchemas(schema, newSchema);
1734      }
1735    }
1736
1737    return schema;
1738  }
1739
1740
1741
1742  /**
1743   * Validates the schema definitions in the specified file.
1744   *
1745   * @param  schemaFile
1746   *              The file containing the schema definitions to validate.  It
1747   *              must not be {@code null}, it must exist, and it must be a
1748   *              file.
1749   * @param  errorMessages
1750   *              A list that will be updated with error messages about any
1751   *              problems identified during processing.  It must not be
1752   *              {@code null}, and it must be updatable.
1753   * @param  existingSchema
1754   *              The existing schema to use in the course of validating
1755   *              definitions.  It may be {@code null} if there is no
1756   *              existing schema and only the definitions read from the
1757   *              provided path should be used.
1758   * @param  schemaFilesProcessed
1759   *              A counter that will be incremented for each schema file that
1760   *              is processed.
1761   * @param  nonSchemaFilesIgnored
1762   *              A list that should be updated with any files that are ignored
1763   *              because they do not match the configured schema file name
1764   *              pattern.
1765   *
1766   * @return  A {@code Schema} object that contains the definitions that were
1767   *          loaded.  If an existing schema was already available, the schema
1768   *          that is returned will be a merged representation of the existing
1769   *          schema and the newly loaded schema.  This may be {@code null} if
1770   *          an error prevented any schema files from being processed and no
1771   *          existing schema was provided.
1772   */
1773  @Nullable()
1774  private Schema validateSchemaFile(@NotNull final File schemaFile,
1775                      @NotNull final List<String> errorMessages,
1776                      @Nullable final Schema existingSchema,
1777                      @NotNull final AtomicInteger schemaFilesProcessed,
1778                      @NotNull final List<File> nonSchemaFilesIgnored)
1779  {
1780    if (schemaFileNamePattern != null)
1781    {
1782      final String name = schemaFile.getName();
1783      if (! schemaFileNamePattern.matcher(name).matches())
1784      {
1785        if (ignoreSchemaFilesNotMatchingFileNamePattern)
1786        {
1787          nonSchemaFilesIgnored.add(schemaFile);
1788        }
1789        else
1790        {
1791          errorMessages.add(
1792               ERR_SCHEMA_VALIDATOR_FILE_NAME_DOES_NOT_MATCH_PATTERN.get(
1793                    schemaFile.getAbsoluteFile().getParentFile().
1794                         getAbsolutePath(),
1795                    name));
1796        }
1797
1798        return existingSchema;
1799      }
1800    }
1801
1802    schemaFilesProcessed.incrementAndGet();
1803
1804    Schema newSchema = existingSchema;
1805    try (LDIFReader ldifReader = new LDIFReader(schemaFile))
1806    {
1807      Entry schemaEntry = ldifReader.readEntry();
1808      if (schemaEntry == null)
1809      {
1810        errorMessages.add(ERR_SCHEMA_VALIDATOR_NO_ENTRY_IN_FILE.get(
1811             schemaFile.getAbsolutePath()));
1812        return existingSchema;
1813      }
1814
1815      newSchema = validateSchemaEntry(schemaEntry, schemaFile, errorMessages,
1816           newSchema);
1817
1818      while (true)
1819      {
1820        schemaEntry = ldifReader.readEntry();
1821        if (schemaEntry == null)
1822        {
1823          break;
1824        }
1825
1826        if (! allowMultipleEntriesPerFile)
1827        {
1828          errorMessages.add(ERR_SCHEMA_VALIDATOR_MULTIPLE_ENTRIES_IN_FILE.get(
1829               schemaFile.getAbsolutePath()));
1830          return newSchema;
1831        }
1832
1833        newSchema = validateSchemaEntry(schemaEntry, schemaFile, errorMessages,
1834             newSchema);
1835      }
1836    }
1837    catch (final IOException e)
1838    {
1839      Debug.debugException(e);
1840      errorMessages.add(ERR_SCHEMA_VALIDATOR_ERROR_READING_FILE.get(
1841           schemaFile.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
1842    }
1843    catch (final LDIFException e)
1844    {
1845      Debug.debugException(e);
1846      errorMessages.add(ERR_SCHEMA_VALIDATOR_MALFORMED_LDIF_ENTRY.get(
1847           schemaFile.getAbsolutePath(), e.getMessage()));
1848    }
1849
1850    return newSchema;
1851  }
1852
1853
1854
1855  /**
1856   * Validates the schema definitions in the provided entry.
1857   *
1858   * @param  schemaEntry
1859   *              The entry containing the schema definitions to validate.  It
1860   *              must not be {@code null}.
1861   * @param  schemaFile
1862   *              The file from which the schema entry was read.  It must not be
1863   *              {@code null}.
1864   * @param  errorMessages
1865   *              A list that will be updated with error messages about any
1866   *              problems identified during processing.  It must not be
1867   *              {@code null}, and it must be updatable.
1868   * @param  existingSchema
1869   *              The existing schema to use in the course of validating
1870   *              definitions.  It may be {@code null} if there is no
1871   *              existing schema and only the definitions read from the
1872   *              provided path should be used.
1873   *
1874   * @return  A {@code Schema} object that contains the definitions that were
1875   *          validated.  If an existing schema was already available, the
1876   *          schema that is returned will be a merged representation of the
1877   *          existing schema and the newly loaded schema.
1878   */
1879  @NotNull()
1880  private Schema validateSchemaEntry(@NotNull final Entry schemaEntry,
1881                      @NotNull final File schemaFile,
1882                      @NotNull final List<String> errorMessages,
1883                      @Nullable final Schema existingSchema)
1884  {
1885    if (schemaEntry.hasAttribute(Schema.ATTR_ATTRIBUTE_SYNTAX))
1886    {
1887      validateAttributeSyntaxes(schemaEntry, schemaFile, errorMessages);
1888    }
1889
1890    if (schemaEntry.hasAttribute(Schema.ATTR_MATCHING_RULE))
1891    {
1892      if (attributeSyntaxMap.isEmpty())
1893      {
1894        configureLDAPSDKDefaultAttributeSyntaxes();
1895      }
1896
1897      validateMatchingRules(schemaEntry, schemaFile, existingSchema,
1898           errorMessages);
1899    }
1900
1901    if (schemaEntry.hasAttribute(Schema.ATTR_ATTRIBUTE_TYPE))
1902    {
1903      if (attributeSyntaxMap.isEmpty())
1904      {
1905        configureLDAPSDKDefaultAttributeSyntaxes();
1906      }
1907
1908      if (matchingRuleMap.isEmpty())
1909      {
1910        configureLDAPSDKDefaultMatchingRules();
1911      }
1912
1913      final Map<String,AttributeTypeDefinition> attributeTypeMap =
1914           new HashMap<>();
1915      validateAttributeTypes(schemaEntry, schemaFile, attributeTypeMap,
1916           existingSchema, errorMessages);
1917    }
1918
1919    if (schemaEntry.hasAttribute(Schema.ATTR_OBJECT_CLASS))
1920    {
1921      final Entry schemaEntryWithoutObjectClasses = schemaEntry.duplicate();
1922      schemaEntryWithoutObjectClasses.removeAttribute(Schema.ATTR_OBJECT_CLASS);
1923      Schema s = new Schema(schemaEntryWithoutObjectClasses);
1924      if (existingSchema != null)
1925      {
1926        s = Schema.mergeSchemas(existingSchema, s);
1927      }
1928
1929      final Map<String,ObjectClassDefinition> objectClassMap = new HashMap<>();
1930      validateObjectClasses(schemaEntry, schemaFile, objectClassMap, s,
1931           errorMessages);
1932    }
1933
1934    if (schemaEntry.hasAttribute(Schema.ATTR_NAME_FORM))
1935    {
1936      final Entry schemaEntryWithoutNameForms = schemaEntry.duplicate();
1937      schemaEntryWithoutNameForms.removeAttribute(Schema.ATTR_NAME_FORM);
1938      Schema s = new Schema(schemaEntryWithoutNameForms);
1939      if (existingSchema != null)
1940      {
1941        s = Schema.mergeSchemas(existingSchema, s);
1942      }
1943
1944      final Map<String,NameFormDefinition> nameFormsByNameOrOID =
1945           new HashMap<>();
1946      final Map<ObjectClassDefinition,NameFormDefinition> nameFormsByOC =
1947           new HashMap<>();
1948      validateNameForms(schemaEntry, schemaFile, nameFormsByNameOrOID,
1949           nameFormsByOC, s, errorMessages);
1950    }
1951
1952    if (schemaEntry.hasAttribute(Schema.ATTR_DIT_CONTENT_RULE))
1953    {
1954      final Entry schemaEntryWithoutDITContentRules = schemaEntry.duplicate();
1955      schemaEntryWithoutDITContentRules.removeAttribute(
1956           Schema.ATTR_DIT_CONTENT_RULE);
1957      Schema s = new Schema(schemaEntryWithoutDITContentRules);
1958      if (existingSchema != null)
1959      {
1960        s = Schema.mergeSchemas(existingSchema, s);
1961      }
1962
1963      final Map<String,DITContentRuleDefinition> dcrMap = new HashMap<>();
1964      validateDITContentRules(schemaEntry, schemaFile, dcrMap, s,
1965           errorMessages);
1966    }
1967
1968    if (schemaEntry.hasAttribute(Schema.ATTR_DIT_STRUCTURE_RULE))
1969    {
1970      final Entry schemaEntryWithoutDITStructureRules = schemaEntry.duplicate();
1971      schemaEntryWithoutDITStructureRules.removeAttribute(
1972           Schema.ATTR_DIT_STRUCTURE_RULE);
1973      Schema s = new Schema(schemaEntryWithoutDITStructureRules);
1974      if (existingSchema != null)
1975      {
1976        s = Schema.mergeSchemas(existingSchema, s);
1977      }
1978
1979      final Map<String,DITStructureRuleDefinition> dsrIDAndNameMap =
1980           new HashMap<>();
1981      final Map<NameFormDefinition,DITStructureRuleDefinition> dsrNFMap =
1982           new HashMap<>();
1983      validateDITStructureRules(schemaEntry, schemaFile, dsrIDAndNameMap,
1984           dsrNFMap, s, errorMessages);
1985    }
1986
1987    if (schemaEntry.hasAttribute(Schema.ATTR_MATCHING_RULE_USE))
1988    {
1989      final Entry schemaEntryWithoutMatchingRuleUses = schemaEntry.duplicate();
1990      schemaEntryWithoutMatchingRuleUses.removeAttribute(
1991           Schema.ATTR_MATCHING_RULE_USE);
1992      Schema s = new Schema(schemaEntryWithoutMatchingRuleUses);
1993      if (existingSchema != null)
1994      {
1995        s = Schema.mergeSchemas(existingSchema, s);
1996      }
1997
1998      final Map<String,MatchingRuleUseDefinition> mruMap = new HashMap<>();
1999      validateMatchingRuleUses(schemaEntry, schemaFile, mruMap, s,
2000           errorMessages);
2001    }
2002
2003    Schema s = new Schema(schemaEntry);
2004    if (existingSchema != null)
2005    {
2006      s = Schema.mergeSchemas(existingSchema, s);
2007    }
2008
2009
2010    if (ensureSchemaEntryIsValid)
2011    {
2012      final List<String> schemaEntryInvalidReasons = new ArrayList<>();
2013
2014      final EntryValidator entryValidator = new EntryValidator(s);
2015      if (! entryValidator.entryIsValid(schemaEntry, schemaEntryInvalidReasons))
2016      {
2017        for (final String invalidReason : schemaEntryInvalidReasons)
2018        {
2019          errorMessages.add(ERR_SCHEMA_VALIDATOR_ENTRY_NOT_VALID.get(
2020               schemaEntry.getDN(), schemaFile.getAbsolutePath(),
2021               invalidReason));
2022        }
2023      }
2024    }
2025
2026    return s;
2027  }
2028
2029
2030
2031  /**
2032   * Validates any attribute syntax definitions contained in the provided
2033   * schema entry.
2034   *
2035   * @param  schemaEntry
2036   *              The entry containing the schema definitions to validate.  It
2037   *              must not be {@code null}.
2038   * @param  schemaFile
2039   *              The file from which the schema entry was read.  It must not be
2040   *              {@code null}.
2041   * @param  errorMessages
2042   *              A list that will be updated with error messages about any
2043   *              problems identified during processing.  It must not be
2044   *              {@code null}, and it must be updatable.
2045   */
2046  private void validateAttributeSyntaxes(@NotNull final Entry schemaEntry,
2047                    @NotNull final File schemaFile,
2048                    @NotNull final List<String> errorMessages)
2049  {
2050    for (final String syntaxString :
2051         schemaEntry.getAttributeValues(Schema.ATTR_ATTRIBUTE_SYNTAX))
2052    {
2053      // If attribute syntaxes aren't allowed, then report an error without
2054      // doing anything else.
2055      if (! allowedSchemaElementTypes.contains(
2056           SchemaElementType.ATTRIBUTE_SYNTAX))
2057      {
2058        errorMessages.add(ERR_SCHEMA_VALIDATOR_SYNTAX_NOT_ALLOWED.get(
2059             schemaFile.getAbsolutePath(), syntaxString));
2060        continue;
2061      }
2062
2063
2064      // Make sure that we can parse the syntax definition.
2065      final AttributeSyntaxDefinition syntax;
2066      try
2067      {
2068        syntax = new AttributeSyntaxDefinition(syntaxString);
2069      }
2070      catch (final LDAPException e)
2071      {
2072        Debug.debugException(e);
2073        errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_SYNTAX.get(
2074             syntaxString, schemaFile.getAbsolutePath(), e.getMessage()));
2075        continue;
2076      }
2077
2078
2079      // Make sure that the syntax has a valid numeric OID.
2080      try
2081      {
2082        validateOID(syntax.getOID(), StaticUtils.NO_STRINGS);
2083      }
2084      catch (final ParseException e)
2085      {
2086        Debug.debugException(e);
2087        errorMessages.add(ERR_SCHEMA_VALIDATOR_SYNTAX_INVALID_OID.get(
2088             syntaxString, schemaFile.getAbsolutePath(), e.getMessage()));
2089      }
2090
2091
2092      // If the syntax has a description, then make sure it's not empty.
2093      if (! allowEmptyDescription)
2094      {
2095        final String description = syntax.getDescription();
2096        if ((description != null) && description.isEmpty())
2097        {
2098          errorMessages.add(ERR_SCHEMA_VALIDATOR_SYNTAX_EMPTY_DESCRIPTION.get(
2099               syntaxString, schemaFile.getAbsolutePath()));
2100        }
2101      }
2102
2103
2104
2105      // Make sure that the syntax isn't already defined.
2106      final String lowerOID = StaticUtils.toLowerCase(syntax.getOID());
2107      final AttributeSyntaxDefinition existingSyntax =
2108           attributeSyntaxMap.get(lowerOID);
2109      if ((existingSyntax != null) && (! allowRedefiningElements))
2110      {
2111        errorMessages.add(ERR_SCHEMA_VALIDATOR_SYNTAX_ALREADY_DEFINED.get(
2112             syntaxString, schemaFile.getAbsolutePath(),
2113             existingSyntax.toString()));
2114      }
2115
2116      attributeSyntaxMap.put(lowerOID, syntax);
2117    }
2118 }
2119
2120
2121
2122  /**
2123   * Validates any matching rule definitions contained in the provided schema
2124   * entry.
2125   *
2126   * @param  schemaEntry
2127   *              The entry containing the schema definitions to validate.  It
2128   *              must not be {@code null}.
2129   * @param  schemaFile
2130   *              The file from which the schema entry was read.  It must not be
2131   *              {@code null}.
2132   * @param  existingSchema
2133   *              An existing schema that has already been read (e.g., from
2134   *              earlier schema files).  It may be {@code null} if only the
2135   *              elements from the current file should be used.
2136   * @param  errorMessages
2137   *              A list that will be updated with error messages about any
2138   *              problems identified during processing.  It must not be
2139   *              {@code null}, and it must be updatable.
2140   */
2141  private void validateMatchingRules(@NotNull final Entry schemaEntry,
2142                    @NotNull final File schemaFile,
2143                    @Nullable final Schema existingSchema,
2144                    @NotNull final List<String> errorMessages)
2145  {
2146    for (final String matchingRuleString :
2147         schemaEntry.getAttributeValues(Schema.ATTR_MATCHING_RULE))
2148    {
2149      // If matching rules aren't allowed, then report an error without doing
2150      // anything else.
2151      if (! allowedSchemaElementTypes.contains(
2152           SchemaElementType.MATCHING_RULE))
2153      {
2154        errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_NOT_ALLOWED.get(
2155             schemaFile.getAbsolutePath(), matchingRuleString));
2156        continue;
2157      }
2158
2159
2160      // Make sure that we can parse the matching rule definition.
2161      final MatchingRuleDefinition matchingRule;
2162      try
2163      {
2164        matchingRule = new MatchingRuleDefinition(matchingRuleString);
2165      }
2166      catch (final LDAPException e)
2167      {
2168        Debug.debugException(e);
2169        errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_MR.get(
2170             matchingRuleString, schemaFile.getAbsolutePath(), e.getMessage()));
2171        continue;
2172      }
2173
2174
2175      // Make sure that the matching rule has a valid numeric OID.
2176      try
2177      {
2178        validateOID(matchingRule.getOID(), matchingRule.getNames());
2179      }
2180      catch (final ParseException e)
2181      {
2182        Debug.debugException(e);
2183        errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_INVALID_OID.get(
2184             matchingRuleString, schemaFile.getAbsolutePath(), e.getMessage()));
2185      }
2186
2187
2188      // Make sure that all of the names are valid.
2189      if ((matchingRule.getNames().length == 0) &&
2190           (! allowElementsWithoutNames))
2191      {
2192        errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_NO_NAME.get(
2193             matchingRuleString, schemaFile.getAbsolutePath()));
2194      }
2195
2196      for (final String name : matchingRule.getNames())
2197      {
2198        try
2199        {
2200          validateName(name);
2201        }
2202        catch (final ParseException e)
2203        {
2204          Debug.debugException(e);
2205          errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_INVALID_NAME.get(
2206               matchingRuleString, schemaFile.getAbsolutePath(), name,
2207               e.getMessage()));
2208        }
2209      }
2210
2211
2212      // If the matching rule has a description, then make sure it's not empty.
2213      if (! allowEmptyDescription)
2214      {
2215        final String description = matchingRule.getDescription();
2216        if ((description != null) && description.isEmpty())
2217        {
2218          errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_EMPTY_DESCRIPTION.get(
2219               matchingRuleString, schemaFile.getAbsolutePath()));
2220        }
2221      }
2222
2223
2224      // If the matching rule is declared obsolete, then make sure that's
2225      // allowed.
2226      if (matchingRule.isObsolete() && (! allowObsoleteElements))
2227      {
2228        errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_OBSOLETE.get(
2229             matchingRuleString, schemaFile.getAbsolutePath()));
2230      }
2231
2232
2233      // Make sure that the syntax OID is valid.
2234      final String syntaxOID = matchingRule.getSyntaxOID();
2235      try
2236      {
2237        validateOID(syntaxOID, StaticUtils.NO_STRINGS);
2238      }
2239      catch (final ParseException e)
2240      {
2241        Debug.debugException(e);
2242        errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_INVALID_SYNTAX_OID.get(
2243             matchingRuleString, schemaFile.getAbsolutePath(), syntaxOID,
2244             e.getMessage()));
2245      }
2246
2247
2248      // Make sure that the syntax OID is one that we know about.
2249      if (! allowReferencesToUndefinedElementTypes.contains(
2250           SchemaElementType.ATTRIBUTE_SYNTAX))
2251      {
2252        final String lowerSyntaxOID = StaticUtils.toLowerCase(syntaxOID);
2253        if (! attributeSyntaxMap.containsKey(lowerSyntaxOID))
2254        {
2255          if ((existingSchema == null) ||
2256               existingSchema.getAttributeSyntax(lowerSyntaxOID) == null)
2257          {
2258            errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_UNDEFINED_SYNTAX.get(
2259                 matchingRuleString, schemaFile.getAbsolutePath(), syntaxOID));
2260          }
2261        }
2262      }
2263
2264
2265      // Make sure that the matching rule isn't already defined.
2266      boolean isDuplicate = false;
2267      final String lowerOID = StaticUtils.toLowerCase(matchingRule.getOID());
2268      if (matchingRuleMap.containsKey(lowerOID) && (! allowRedefiningElements))
2269      {
2270        errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_ALREADY_DEFINED_WITH_OID.get(
2271             matchingRuleString, schemaFile.getAbsolutePath(),
2272             matchingRuleMap.get(lowerOID).toString()));
2273        isDuplicate = true;
2274      }
2275
2276      if (! isDuplicate)
2277      {
2278        for (final String name : matchingRule.getNames())
2279        {
2280          final String lowerName = StaticUtils.toLowerCase(name);
2281          if (matchingRuleMap.containsKey(lowerName) &&
2282               (! allowRedefiningElements))
2283          {
2284            errorMessages.add(
2285                 ERR_SCHEMA_VALIDATOR_MR_ALREADY_DEFINED_WITH_NAME.get(
2286                      matchingRuleString, schemaFile.getAbsolutePath(),
2287                      name, matchingRuleMap.get(lowerName).toString()));
2288            isDuplicate = true;
2289            break;
2290          }
2291        }
2292      }
2293
2294      if (! isDuplicate)
2295      {
2296        matchingRuleMap.put(lowerOID, matchingRule);
2297        for (final String name : matchingRule.getNames())
2298        {
2299          matchingRuleMap.put(StaticUtils.toLowerCase(name), matchingRule);
2300        }
2301      }
2302    }
2303  }
2304
2305
2306
2307  /**
2308   * Validates any attribute type definitions contained in the provided schema
2309   * entry.
2310   *
2311   * @param  schemaEntry
2312   *              The entry containing the schema definitions to validate.  It
2313   *              must not be {@code null}.
2314   * @param  schemaFile
2315   *              The file from which the schema entry was read.  It must not be
2316   *              {@code null}.
2317   * @param  attributeTypeMap
2318   *              A map of the attribute type definitions that have already been
2319   *              parsed from the same file.  It must not be {@code null} (but
2320   *              may be empty), and it must be updatable.
2321   * @param  existingSchema
2322   *              An existing schema that has already been read (e.g., from
2323   *              earlier schema files).  It may be {@code null} if only the
2324   *              elements from the current file should be used.
2325   * @param  errorMessages
2326   *              A list that will be updated with error messages about any
2327   *              problems identified during processing.  It must not be
2328   *              {@code null}, and it must be updatable.
2329   */
2330  private void validateAttributeTypes(@NotNull final Entry schemaEntry,
2331       @NotNull final File schemaFile,
2332       @NotNull final Map<String,AttributeTypeDefinition> attributeTypeMap,
2333       @Nullable final Schema existingSchema,
2334       @NotNull final List<String> errorMessages)
2335  {
2336    for (final String attributeTypeString :
2337         schemaEntry.getAttributeValues(Schema.ATTR_ATTRIBUTE_TYPE))
2338    {
2339      // If attribute types aren't allowed, then report an error without doing
2340      // anything else.
2341      if (! allowedSchemaElementTypes.contains(
2342           SchemaElementType.ATTRIBUTE_TYPE))
2343      {
2344        errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_NOT_ALLOWED.get(
2345             schemaFile.getAbsolutePath(), attributeTypeString));
2346        continue;
2347      }
2348
2349
2350      // Make sure that we can parse the attribute type definition.
2351      final AttributeTypeDefinition attributeType;
2352      try
2353      {
2354        attributeType = new AttributeTypeDefinition(attributeTypeString);
2355      }
2356      catch (final LDAPException e)
2357      {
2358        Debug.debugException(e);
2359        errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_AT.get(
2360             attributeTypeString, schemaFile.getAbsolutePath(),
2361             e.getMessage()));
2362        continue;
2363      }
2364
2365
2366      // Make sure that the attribute type has a valid numeric OID.
2367      try
2368      {
2369        validateOID(attributeType.getOID(), attributeType.getNames());
2370      }
2371      catch (final ParseException e)
2372      {
2373        Debug.debugException(e);
2374        errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_INVALID_OID.get(
2375             attributeTypeString, schemaFile.getAbsolutePath(),
2376             e.getMessage()));
2377      }
2378
2379
2380      // Make sure that all of the names are valid.
2381      if ((attributeType.getNames().length == 0) &&
2382           (! allowElementsWithoutNames))
2383      {
2384        errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_NO_NAME.get(
2385             attributeTypeString, schemaFile.getAbsolutePath()));
2386      }
2387
2388      for (final String name : attributeType.getNames())
2389      {
2390        try
2391        {
2392          validateName(name);
2393        }
2394        catch (final ParseException e)
2395        {
2396          Debug.debugException(e);
2397          errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_INVALID_NAME.get(
2398               attributeTypeString, schemaFile.getAbsolutePath(), name,
2399               e.getMessage()));
2400        }
2401      }
2402
2403
2404      // If the attribute type has a description, then make sure it's not empty.
2405      if (! allowEmptyDescription)
2406      {
2407        final String description = attributeType.getDescription();
2408        if ((description != null) && description.isEmpty())
2409        {
2410          errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_EMPTY_DESCRIPTION.get(
2411               attributeTypeString, schemaFile.getAbsolutePath()));
2412        }
2413      }
2414
2415
2416      // If the attribute type is declared obsolete, then make sure that's
2417      // allowed.
2418      if (attributeType.isObsolete() && (! allowObsoleteElements))
2419      {
2420        errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_OBSOLETE.get(
2421             attributeTypeString, schemaFile.getAbsolutePath()));
2422      }
2423
2424
2425      // Check to see if there is a superior type, and if so, whether it's
2426      // defined.
2427      AttributeTypeDefinition superiorType;
2428      final String superiorTypeNameOrOID = attributeType.getSuperiorType();
2429      if (superiorTypeNameOrOID == null)
2430      {
2431        superiorType = null;
2432      }
2433      else
2434      {
2435        final String lowerSuperiorTypeNameOrOID =
2436             StaticUtils.toLowerCase(superiorTypeNameOrOID);
2437        superiorType = attributeTypeMap.get(lowerSuperiorTypeNameOrOID);
2438        if ((superiorType == null) && (existingSchema != null))
2439        {
2440          superiorType =
2441               existingSchema.getAttributeType(lowerSuperiorTypeNameOrOID);
2442        }
2443
2444        if ((superiorType == null) &&
2445             (! allowReferencesToUndefinedElementTypes.contains(
2446                  SchemaElementType.ATTRIBUTE_TYPE)))
2447        {
2448          errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_SUPERIOR.get(
2449               attributeTypeString, schemaFile.getAbsolutePath(),
2450               superiorTypeNameOrOID));
2451        }
2452      }
2453
2454
2455      // Check to see if there is an equality matching rule.  If not, then we
2456      // may want to check to make sure that there is a superior type because
2457      // an attribute type without an equality matching rule can be problematic.
2458      final String equalityMRNameOrOID =
2459           attributeType.getEqualityMatchingRule();
2460      if (equalityMRNameOrOID == null)
2461      {
2462        if ((superiorTypeNameOrOID == null) &&
2463             (! allowAttributeTypesWithoutEqualityMatchingRule))
2464        {
2465          errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_NO_EQ_MR.get(
2466               attributeTypeString, schemaFile.getAbsolutePath()));
2467        }
2468      }
2469
2470
2471      // Check to make sure that any declared matching rules are defined in the
2472      // schema.
2473      if (! allowReferencesToUndefinedElementTypes.contains(
2474           SchemaElementType.MATCHING_RULE))
2475      {
2476        if (equalityMRNameOrOID != null)
2477        {
2478          if (! matchingRuleMap.containsKey(
2479               StaticUtils.toLowerCase(equalityMRNameOrOID)))
2480          {
2481            if ((existingSchema == null) ||
2482                 (existingSchema.getMatchingRule(equalityMRNameOrOID) == null))
2483            {
2484              errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_EQ_MR.get(
2485                   attributeTypeString, schemaFile.getAbsolutePath(),
2486                   equalityMRNameOrOID));
2487            }
2488          }
2489        }
2490
2491        final String orderingMRNameOrOID =
2492             attributeType.getOrderingMatchingRule();
2493        if (orderingMRNameOrOID != null)
2494        {
2495          if (! matchingRuleMap.containsKey(
2496               StaticUtils.toLowerCase(orderingMRNameOrOID)))
2497          {
2498            if ((existingSchema == null) ||
2499                 (existingSchema.getMatchingRule(orderingMRNameOrOID) == null))
2500            {
2501              errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_ORD_MR.get(
2502                   attributeTypeString, schemaFile.getAbsolutePath(),
2503                   orderingMRNameOrOID));
2504            }
2505          }
2506        }
2507
2508        final String substringMRNameOrOID =
2509             attributeType.getSubstringMatchingRule();
2510        if (substringMRNameOrOID != null)
2511        {
2512          if (! matchingRuleMap.containsKey(
2513               StaticUtils.toLowerCase(substringMRNameOrOID)))
2514          {
2515            if ((existingSchema == null) ||
2516                 (existingSchema.getMatchingRule(substringMRNameOrOID) == null))
2517            {
2518              errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_SUB_MR.get(
2519                   attributeTypeString, schemaFile.getAbsolutePath(),
2520                   substringMRNameOrOID));
2521            }
2522          }
2523        }
2524      }
2525
2526
2527      // Check to see if there's a syntax.  If not, make sure there's a
2528      // superior type.  Otherwise, make sure the syntax OID is valid and
2529      // references a known syntax.
2530      final String syntaxOID = attributeType.getSyntaxOID();
2531      if (syntaxOID == null)
2532      {
2533        if ((superiorTypeNameOrOID == null) &&
2534             (! allowAttributeTypesWithoutSyntax))
2535        {
2536          errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_NO_SYNTAX.get(
2537               attributeTypeString, schemaFile.getAbsolutePath()));
2538        }
2539      }
2540      else if (! allowReferencesToUndefinedElementTypes.contains(
2541           SchemaElementType.ATTRIBUTE_SYNTAX))
2542      {
2543        final String baseOID =
2544             AttributeTypeDefinition.getBaseSyntaxOID(syntaxOID);
2545        try
2546        {
2547          validateOID(baseOID, StaticUtils.NO_STRINGS);
2548        }
2549        catch (final ParseException e)
2550        {
2551          Debug.debugException(e);
2552          errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_INVALID_SYNTAX_OID.get(
2553               attributeTypeString, schemaFile.getAbsolutePath(), baseOID,
2554               e.getMessage()));
2555        }
2556
2557        final String lowerSyntaxOID = StaticUtils.toLowerCase(baseOID);
2558        if (! attributeSyntaxMap.containsKey(lowerSyntaxOID))
2559        {
2560          if ((existingSchema == null) ||
2561               (existingSchema.getAttributeSyntax(lowerSyntaxOID) == null))
2562          {
2563            errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_SYNTAX.get(
2564                 attributeTypeString, schemaFile.getAbsolutePath(), baseOID));
2565          }
2566        }
2567      }
2568
2569
2570      // Check to see if the attribute type is collective, and if so whether
2571      // that is allowed.
2572      if (attributeType.isCollective() && (! allowCollectiveAttributes))
2573      {
2574        errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_COLLECTIVE.get(
2575             attributeTypeString, schemaFile.getAbsolutePath()));
2576      }
2577
2578
2579      // Check to see if the attribute type is declared as NO-USER-MODIFICATION,
2580      // and if so, then make sure it has an operational usage.
2581      if (attributeType.isNoUserModification() &&
2582           (! attributeType.isOperational()))
2583      {
2584        errorMessages.add(
2585             ERR_SCHEMA_VALIDATOR_AT_NO_USER_MOD_WITHOUT_OP_USAGE.get(
2586                  attributeTypeString, schemaFile.getAbsolutePath()));
2587      }
2588
2589
2590      // Make sure that the attribute type isn't already defined.
2591      boolean isDuplicate = false;
2592      if (! allowRedefiningElements)
2593      {
2594        final String lowerOID = StaticUtils.toLowerCase(attributeType.getOID());
2595        AttributeTypeDefinition existingDefinition =
2596             attributeTypeMap.get(lowerOID);
2597        if ((existingDefinition == null) && (existingSchema != null))
2598        {
2599          existingDefinition = existingSchema.getAttributeType(lowerOID);
2600        }
2601
2602        if (existingDefinition != null)
2603        {
2604          errorMessages.add(
2605               ERR_SCHEMA_VALIDATOR_AT_ALREADY_DEFINED_WITH_OID.get(
2606                    attributeTypeString, schemaFile.getAbsolutePath(),
2607                    existingDefinition.toString()));
2608          isDuplicate = true;
2609        }
2610
2611        if (! isDuplicate)
2612        {
2613          for (final String name : attributeType.getNames())
2614          {
2615            final String lowerName = StaticUtils.toLowerCase(name);
2616            existingDefinition = attributeTypeMap.get(lowerName);
2617            if ((existingDefinition == null) && (existingSchema != null))
2618            {
2619              existingDefinition = existingSchema.getAttributeType(lowerName);
2620            }
2621
2622            if (existingDefinition != null)
2623            {
2624              errorMessages.add(
2625                   ERR_SCHEMA_VALIDATOR_AT_ALREADY_DEFINED_WITH_NAME.get(
2626                        attributeTypeString, schemaFile.getAbsolutePath(),
2627                        name, existingDefinition.toString()));
2628              isDuplicate = true;
2629              break;
2630            }
2631          }
2632        }
2633      }
2634
2635
2636      // Add the attribute type to the map so it can be referenced by
2637      // subordinate types.
2638      if (! isDuplicate)
2639      {
2640        attributeTypeMap.put(StaticUtils.toLowerCase(attributeType.getOID()),
2641             attributeType);
2642        for (final String name : attributeType.getNames())
2643        {
2644          attributeTypeMap.put(StaticUtils.toLowerCase(name), attributeType);
2645        }
2646      }
2647    }
2648  }
2649
2650
2651
2652  /**
2653   * Validates any object class definitions contained in the provided schema
2654   * entry.
2655   *
2656   * @param  schemaEntry
2657   *              The entry containing the schema definitions to validate.  It
2658   *              must not be {@code null}.
2659   * @param  schemaFile
2660   *              The file from which the schema entry was read.  It must not be
2661   *              {@code null}.
2662   * @param  objectClassMap
2663   *              A map of the object class definitions that have already been
2664   *              parsed from the same file.  It must not be {@code null} (but
2665   *              may be empty), and it must be updatable.
2666   * @param  existingSchema
2667   *              An existing schema that has already been read (e.g., from
2668   *              earlier schema files).  It must not be {@code null}.
2669   * @param  errorMessages
2670   *              A list that will be updated with error messages about any
2671   *              problems identified during processing.  It must not be
2672   *              {@code null}, and it must be updatable.
2673   */
2674  private void validateObjectClasses(@NotNull final Entry schemaEntry,
2675       @NotNull final File schemaFile,
2676       @NotNull final Map<String,ObjectClassDefinition> objectClassMap,
2677       @Nullable final Schema existingSchema,
2678       @NotNull final List<String> errorMessages)
2679  {
2680    for (final String objectClassString :
2681         schemaEntry.getAttributeValues(Schema.ATTR_OBJECT_CLASS))
2682    {
2683      // If object classes aren't allowed, then report an error without doing
2684      // anything else.
2685      if (! allowedSchemaElementTypes.contains(SchemaElementType.OBJECT_CLASS))
2686      {
2687        errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_NOT_ALLOWED.get(
2688             schemaFile.getAbsolutePath(), objectClassString));
2689        continue;
2690      }
2691
2692
2693      // Make sure that we can parse the object class definition.
2694      final ObjectClassDefinition objectClass;
2695      try
2696      {
2697        objectClass = new ObjectClassDefinition(objectClassString);
2698      }
2699      catch (final LDAPException e)
2700      {
2701        Debug.debugException(e);
2702        errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_OC.get(
2703             objectClassString, schemaFile.getAbsolutePath(), e.getMessage()));
2704        continue;
2705      }
2706
2707
2708      // Make sure that the object class has a valid numeric OID.
2709      try
2710      {
2711        validateOID(objectClass.getOID(), objectClass.getNames());
2712      }
2713      catch (final ParseException e)
2714      {
2715        Debug.debugException(e);
2716        errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_INVALID_OID.get(
2717             objectClassString, schemaFile.getAbsolutePath(), e.getMessage()));
2718      }
2719
2720
2721      // Make sure that all of the names are valid.
2722      if ((objectClass.getNames().length == 0) &&
2723           (! allowElementsWithoutNames))
2724      {
2725        errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_NO_NAME.get(
2726             objectClassString, schemaFile.getAbsolutePath()));
2727      }
2728
2729      for (final String name : objectClass.getNames())
2730      {
2731        try
2732        {
2733          validateName(name);
2734        }
2735        catch (final ParseException e)
2736        {
2737          Debug.debugException(e);
2738          errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_INVALID_NAME.get(
2739               objectClassString, schemaFile.getAbsolutePath(), name,
2740               e.getMessage()));
2741        }
2742      }
2743
2744
2745      // If the object class has a description, then make sure it's not empty.
2746      if (! allowEmptyDescription)
2747      {
2748        final String description = objectClass.getDescription();
2749        if ((description != null) && description.isEmpty())
2750        {
2751          errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_EMPTY_DESCRIPTION.get(
2752               objectClassString, schemaFile.getAbsolutePath()));
2753        }
2754      }
2755
2756
2757      // If the object class is declared obsolete, then make sure that's
2758      // allowed.
2759      if (objectClass.isObsolete() && (! allowObsoleteElements))
2760      {
2761        errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_OBSOLETE.get(
2762             objectClassString, schemaFile.getAbsolutePath()));
2763      }
2764
2765
2766      // Validate all of the superior object classes.
2767      validateSuperiorObjectClasses(schemaFile, objectClass, objectClassMap,
2768           existingSchema, errorMessages);
2769
2770
2771      // Validate all of the required and optional attribute types.
2772      final Set<String> requiredAttrNamesAndOIDs = new HashSet<>();
2773      for (final String attrNameOrOID : objectClass.getRequiredAttributes())
2774      {
2775        requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(attrNameOrOID));
2776        final AttributeTypeDefinition at =
2777             existingSchema.getAttributeType(attrNameOrOID);
2778        if (at == null)
2779        {
2780          if (! allowReferencesToUndefinedElementTypes.contains(
2781               SchemaElementType.ATTRIBUTE_TYPE))
2782          {
2783            errorMessages.add(
2784                 ERR_SCHEMA_VALIDATOR_OC_UNDEFINED_REQUIRED_ATTR.get(
2785                      objectClassString, schemaFile.getAbsolutePath(),
2786                      attrNameOrOID));
2787          }
2788        }
2789        else
2790        {
2791          requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(at.getOID()));
2792          for (final String name : at.getNames())
2793          {
2794            requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(name));
2795          }
2796        }
2797      }
2798
2799      for (final String attrNameOrOID : objectClass.getOptionalAttributes())
2800      {
2801        if (requiredAttrNamesAndOIDs.contains(
2802             StaticUtils.toLowerCase(attrNameOrOID)))
2803        {
2804          errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_AT_REQ_AND_OPT.get(
2805               objectClassString, schemaFile.getAbsolutePath(),
2806               attrNameOrOID));
2807        }
2808
2809        final AttributeTypeDefinition at =
2810             existingSchema.getAttributeType(attrNameOrOID);
2811        if ((at == null) &&
2812             (! allowReferencesToUndefinedElementTypes.contains(
2813                  SchemaElementType.ATTRIBUTE_TYPE)))
2814        {
2815          errorMessages.add(
2816               ERR_SCHEMA_VALIDATOR_OC_UNDEFINED_OPTIONAL_ATTR.get(
2817                    objectClassString, schemaFile.getAbsolutePath(),
2818                    attrNameOrOID));
2819        }
2820      }
2821
2822
2823      // Make sure that the object class isn't already defined.
2824      boolean isDuplicate = false;
2825      if (! allowRedefiningElements)
2826      {
2827        final String lowerOID = StaticUtils.toLowerCase(objectClass.getOID());
2828        ObjectClassDefinition existingDefinition =
2829             objectClassMap.get(lowerOID);
2830        if (existingDefinition == null)
2831        {
2832          existingDefinition = existingSchema.getObjectClass(lowerOID);
2833        }
2834
2835        if (existingDefinition != null)
2836        {
2837          errorMessages.add(
2838               ERR_SCHEMA_VALIDATOR_OC_ALREADY_DEFINED_WITH_OID.get(
2839                    objectClassString, schemaFile.getAbsolutePath(),
2840                    existingDefinition.toString()));
2841          isDuplicate = true;
2842        }
2843
2844        if (! isDuplicate)
2845        {
2846          for (final String name : objectClass.getNames())
2847          {
2848            final String lowerName = StaticUtils.toLowerCase(name);
2849            existingDefinition = objectClassMap.get(lowerName);
2850            if (existingDefinition == null)
2851            {
2852              existingDefinition = existingSchema.getObjectClass(lowerName);
2853            }
2854
2855            if (existingDefinition != null)
2856            {
2857              errorMessages.add(
2858                   ERR_SCHEMA_VALIDATOR_OC_ALREADY_DEFINED_WITH_NAME.get(
2859                        objectClassString, schemaFile.getAbsolutePath(),
2860                        name, existingDefinition.toString()));
2861              isDuplicate = true;
2862              break;
2863            }
2864          }
2865        }
2866      }
2867
2868
2869      // Add the object class to the map so it can be referenced by subordinate
2870      // classes.
2871      if (! isDuplicate)
2872      {
2873        objectClassMap.put(StaticUtils.toLowerCase(objectClass.getOID()),
2874             objectClass);
2875        for (final String name : objectClass.getNames())
2876        {
2877          objectClassMap.put(StaticUtils.toLowerCase(name), objectClass);
2878        }
2879      }
2880    }
2881  }
2882
2883
2884
2885  /**
2886   * Retrieves the definitions for the superior object classes for the provided
2887   * object class.
2888   *
2889   * @param  schemaFile
2890   *              The file from which the object class was read.  It must not be
2891   *              {@code null}.
2892   * @param  objectClass
2893   *              The object class for which to retrieve the superior classes.
2894   *              It must not be {@code null}.
2895   * @param  objectClassMap
2896   *              A map of the object class definitions that have already been
2897   *              parsed from the same file.  It must not be {@code null} (but
2898   *              may be empty), and it must be updatable.
2899   * @param  existingSchema
2900   *              An existing schema that has already been read (e.g., from
2901   *              earlier schema files).  It must not be {@code null} .
2902   * @param  errorMessages
2903   *              A list that will be updated with error messages about any
2904   *              problems identified during processing.  It must not be
2905   *              {@code null}, and it must be updatable.
2906   */
2907  private void validateSuperiorObjectClasses(@NotNull final File schemaFile,
2908       @NotNull final ObjectClassDefinition objectClass,
2909       @NotNull final Map<String,ObjectClassDefinition> objectClassMap,
2910       @NotNull final Schema existingSchema,
2911       @NotNull final List<String> errorMessages)
2912  {
2913    // If the object class does not define any superior classes, then determine
2914    // if that's okay.
2915    final String[] superiorClassNamesOrOIDs =
2916         objectClass.getSuperiorClasses();
2917    if (superiorClassNamesOrOIDs.length == 0)
2918    {
2919      if (! allowStructuralObjectClassWithoutSuperior)
2920      {
2921        final ObjectClassType type = objectClass.getObjectClassType();
2922        if (type == null)
2923        {
2924          errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_NO_SUP_NULL_TYPE.get(
2925               objectClass.toString(), schemaFile.getAbsolutePath()));
2926        }
2927        else if (type == ObjectClassType.STRUCTURAL)
2928        {
2929          errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_NO_SUP_STRUCTURAL_TYPE.get(
2930               objectClass.toString(), schemaFile.getAbsolutePath()));
2931        }
2932      }
2933
2934      return;
2935    }
2936
2937
2938    // If the object class has multiple superior classes, then determine if
2939    // that's okay.
2940    if ((superiorClassNamesOrOIDs.length > 1) &&
2941         (! allowMultipleSuperiorObjectClasses))
2942    {
2943      errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_MULTIPLE_SUP.get(
2944           objectClass.toString(), schemaFile.getAbsolutePath()));
2945    }
2946
2947
2948    // Make sure that we can retrieve all of the superior classes.
2949    final Map<String,ObjectClassDefinition> superiorClasses =
2950         new LinkedHashMap<>();
2951    for (final String nameOrOID : superiorClassNamesOrOIDs)
2952    {
2953      final String lowerNameOrOID = StaticUtils.toLowerCase(nameOrOID);
2954      ObjectClassDefinition superiorClass = objectClassMap.get(lowerNameOrOID);
2955      if (superiorClass == null)
2956      {
2957        superiorClass = existingSchema.getObjectClass(lowerNameOrOID);
2958      }
2959
2960      if (superiorClass == null)
2961      {
2962        if (! allowReferencesToUndefinedElementTypes.contains(
2963             SchemaElementType.OBJECT_CLASS))
2964        {
2965          errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_UNDEFINED_SUP.get(
2966               objectClass.toString(), schemaFile.getAbsolutePath(),
2967               nameOrOID));
2968        }
2969      }
2970      else
2971      {
2972        superiorClasses.put(nameOrOID, superiorClass);
2973      }
2974    }
2975
2976    // If we should verify the superior relationships, then do that now.
2977    if ((! allowInvalidObjectClassInheritance) && (! superiorClasses.isEmpty()))
2978    {
2979      if (objectClass.getObjectClassType() == null)
2980      {
2981        for (final Map.Entry<String,ObjectClassDefinition> e :
2982             superiorClasses.entrySet())
2983        {
2984          if (e.getValue().getObjectClassType() == ObjectClassType.AUXILIARY)
2985          {
2986            errorMessages.add(
2987                 ERR_SCHEMA_VALIDATOR_OC_IMPLIED_STRUCTURAL_SUP_OF_AUXILIARY.
2988                      get(objectClass.toString(), schemaFile.getAbsolutePath(),
2989                           e.getKey()));
2990            break;
2991          }
2992        }
2993      }
2994      else
2995      {
2996        switch (objectClass.getObjectClassType())
2997        {
2998          case STRUCTURAL:
2999            for (final Map.Entry<String,ObjectClassDefinition>  e :
3000                 superiorClasses.entrySet())
3001            {
3002              if (e.getValue().getObjectClassType() ==
3003                   ObjectClassType.AUXILIARY)
3004              {
3005                errorMessages.add(
3006                     ERR_SCHEMA_VALIDATOR_OC_STRUCTURAL_SUP_OF_AUXILIARY.get(
3007                          objectClass.toString(), schemaFile.getAbsolutePath(),
3008                          e.getKey()));
3009                break;
3010              }
3011            }
3012            break;
3013
3014          case AUXILIARY:
3015            for (final Map.Entry<String,ObjectClassDefinition>  e :
3016                 superiorClasses.entrySet())
3017            {
3018              if (e.getValue().getObjectClassType() ==
3019                   ObjectClassType.STRUCTURAL)
3020              {
3021                errorMessages.add(
3022                     ERR_SCHEMA_VALIDATOR_OC_AUXILIARY_SUP_OF_STRUCTURAL.get(
3023                          objectClass.toString(), schemaFile.getAbsolutePath(),
3024                          e.getKey()));
3025                break;
3026              }
3027            }
3028            break;
3029
3030          case ABSTRACT:
3031            for (final Map.Entry<String,ObjectClassDefinition>  e :
3032                 superiorClasses.entrySet())
3033            {
3034              if (e.getValue().getObjectClassType() ==
3035                   ObjectClassType.STRUCTURAL)
3036              {
3037                errorMessages.add(
3038                     ERR_SCHEMA_VALIDATOR_OC_ABSTRACT_SUP_OF_STRUCTURAL.get(
3039                          objectClass.toString(), schemaFile.getAbsolutePath(),
3040                          e.getKey()));
3041                break;
3042              }
3043              else if (e.getValue().getObjectClassType() ==
3044                   ObjectClassType.AUXILIARY)
3045              {
3046                errorMessages.add(
3047                     ERR_SCHEMA_VALIDATOR_OC_ABSTRACT_SUP_OF_AUXILIARY.get(
3048                          objectClass.toString(), schemaFile.getAbsolutePath(),
3049                          e.getKey()));
3050                break;
3051              }
3052            }
3053            break;
3054        }
3055      }
3056    }
3057  }
3058
3059
3060
3061  /**
3062   * Validates any name form definitions contained in the provided schema entry.
3063   *
3064   * @param  schemaEntry
3065   *              The entry containing the schema definitions to validate.  It
3066   *              must not be {@code null}.
3067   * @param  schemaFile
3068   *              The file from which the schema entry was read.  It must not be
3069   *              {@code null}.
3070   * @param  nameFormsByNameOrOID
3071   *              A map of the name form definitions that have already
3072   *              been parsed from the same file, indexed by OID and names.  It
3073   *              must not be {@code null} (but may be empty), and it must be
3074   *              updatable.
3075   * @param  nameFormsByOC
3076   *              A map of the name form definitions that have already
3077   *              been parsed from the same file, indexed by structural object
3078   *              class.  It must not be {@code null} (but may be empty), and it
3079   *              must be updatable.
3080   * @param  existingSchema
3081   *              An existing schema that has already been read (e.g., from
3082   *              earlier schema files).  It must not be {@code null}.
3083   * @param  errorMessages
3084   *              A list that will be updated with error messages about any
3085   *              problems identified during processing.  It must not be
3086   *              {@code null}, and it must be updatable.
3087   */
3088  private void validateNameForms(@NotNull final Entry schemaEntry,
3089       @NotNull final File schemaFile,
3090       @NotNull final Map<String,NameFormDefinition> nameFormsByNameOrOID,
3091       @NotNull final Map<ObjectClassDefinition,NameFormDefinition>
3092            nameFormsByOC,
3093       @NotNull final Schema existingSchema,
3094       @NotNull final List<String> errorMessages)
3095  {
3096    for (final String nameFormString :
3097         schemaEntry.getAttributeValues(Schema.ATTR_NAME_FORM))
3098    {
3099      // If name forms aren't allowed, then report an error without doing
3100      // anything else.
3101      if (! allowedSchemaElementTypes.contains(SchemaElementType.NAME_FORM))
3102      {
3103        errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_NOT_ALLOWED.get(
3104             schemaFile.getAbsolutePath(), nameFormString));
3105        continue;
3106      }
3107
3108
3109      // Make sure that we can parse the name form definition.
3110      final NameFormDefinition nameForm;
3111      try
3112      {
3113        nameForm = new NameFormDefinition(nameFormString);
3114      }
3115      catch (final LDAPException e)
3116      {
3117        Debug.debugException(e);
3118        errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_NF.get(
3119             nameFormString, schemaFile.getAbsolutePath(), e.getMessage()));
3120        continue;
3121      }
3122
3123
3124      // Make sure that the name form has a valid numeric OID.
3125      try
3126      {
3127        validateOID(nameForm.getOID(), nameForm.getNames());
3128      }
3129      catch (final ParseException e)
3130      {
3131        Debug.debugException(e);
3132        errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_INVALID_OID.get(
3133             nameFormString, schemaFile.getAbsolutePath(), e.getMessage()));
3134      }
3135
3136
3137      // Make sure that all of the names are valid.
3138      if ((nameForm.getNames().length == 0) && (! allowElementsWithoutNames))
3139      {
3140        errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_NO_NAME.get(nameFormString,
3141             schemaFile.getAbsolutePath()));
3142      }
3143
3144      for (final String name : nameForm.getNames())
3145      {
3146        try
3147        {
3148          validateName(name);
3149        }
3150        catch (final ParseException e)
3151        {
3152          Debug.debugException(e);
3153          errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_INVALID_NAME.get(
3154               nameFormString, schemaFile.getAbsolutePath(), name,
3155               e.getMessage()));
3156        }
3157      }
3158
3159
3160      // If the name form has a description, then make sure it's not empty.
3161      if (! allowEmptyDescription)
3162      {
3163        final String description = nameForm.getDescription();
3164        if ((description != null) && description.isEmpty())
3165        {
3166          errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_EMPTY_DESCRIPTION.get(
3167               nameFormString, schemaFile.getAbsolutePath()));
3168        }
3169      }
3170
3171
3172      // If the name form is declared obsolete, then make sure that's
3173      // allowed.
3174      if (nameForm.isObsolete() && (! allowObsoleteElements))
3175      {
3176        errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_OBSOLETE.get(
3177             nameFormString, schemaFile.getAbsolutePath()));
3178      }
3179
3180
3181      // Make sure that the structural object class is defined and is defined as
3182      // structural.
3183      final String structuralClassNameOrOID = nameForm.getStructuralClass();
3184      final ObjectClassDefinition structuralClass =
3185           existingSchema.getObjectClass(structuralClassNameOrOID);
3186      if (structuralClass == null)
3187      {
3188        if (! allowReferencesToUndefinedElementTypes.contains(
3189             SchemaElementType.OBJECT_CLASS))
3190        {
3191          errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_UNDEFINED_OC.get(
3192               nameFormString, schemaFile.getAbsolutePath(),
3193               structuralClassNameOrOID));
3194        }
3195      }
3196      else
3197      {
3198        if ((structuralClass.getObjectClassType() != null) &&
3199             (structuralClass.getObjectClassType() !=
3200                  ObjectClassType.STRUCTURAL))
3201        {
3202          errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_OC_NOT_STRUCTURAL.get(
3203               nameFormString, schemaFile.getAbsolutePath(),
3204               structuralClassNameOrOID));
3205        }
3206      }
3207
3208
3209      // Make sure that all of the required attribute types are defined and
3210      // permitted by the structural class.
3211      final Set<String> requiredAttrNamesAndOIDs = new HashSet<>();
3212      for (final String attrNameOrOID : nameForm.getRequiredAttributes())
3213      {
3214        requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(attrNameOrOID));
3215        final AttributeTypeDefinition attrType =
3216             existingSchema.getAttributeType(attrNameOrOID);
3217        if (attrType == null)
3218        {
3219          if (! allowReferencesToUndefinedElementTypes.contains(
3220               SchemaElementType.ATTRIBUTE_TYPE))
3221          {
3222            errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_UNDEFINED_REQ_ATTR.get(
3223                 nameFormString, schemaFile.getAbsolutePath(),
3224                 attrNameOrOID));
3225          }
3226        }
3227        else
3228        {
3229          requiredAttrNamesAndOIDs.add(
3230               StaticUtils.toLowerCase(attrType.getOID()));
3231          for (final String name : attrType.getNames())
3232          {
3233            requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(name));
3234          }
3235
3236          if ((structuralClass != null) &&
3237               (! (structuralClass.getRequiredAttributes(existingSchema, true).
3238                    contains(attrType) ||
3239                    structuralClass.getOptionalAttributes(existingSchema, true).
3240                         contains(attrType))))
3241          {
3242            errorMessages.add(
3243                 ERR_SCHEMA_VALIDATOR_NF_REQ_ATTR_NOT_PERMITTED.get(
3244                      nameFormString, schemaFile.getAbsolutePath(),
3245                      attrNameOrOID, structuralClassNameOrOID));
3246          }
3247        }
3248      }
3249
3250
3251      // Make sure that all of the optional attribute types are defined and
3252      // permitted by the structural class.  Also, make sure that none of them
3253      // also appear in the set of required attributes.
3254      for (final String attrNameOrOID : nameForm.getOptionalAttributes())
3255      {
3256        if (requiredAttrNamesAndOIDs.contains(
3257             StaticUtils.toLowerCase(attrNameOrOID)))
3258        {
3259          errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_ATTR_REQ_AND_OPT.get(
3260               nameFormString, schemaFile.getAbsolutePath(), attrNameOrOID));
3261        }
3262
3263        final AttributeTypeDefinition attrType =
3264             existingSchema.getAttributeType(attrNameOrOID);
3265        if (attrType == null)
3266        {
3267          if (! allowReferencesToUndefinedElementTypes.contains(
3268               SchemaElementType.ATTRIBUTE_TYPE))
3269          {
3270            errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_UNDEFINED_OPT_ATTR.get(
3271                 nameFormString, schemaFile.getAbsolutePath(),
3272                 attrNameOrOID));
3273          }
3274        }
3275      }
3276
3277
3278      // Make sure that the name form isn't already defined by OID, name, or
3279      // structural class.
3280      boolean isDuplicate = false;
3281      if (! allowRedefiningElements)
3282      {
3283        final String lowerOID = StaticUtils.toLowerCase(nameForm.getOID());
3284        NameFormDefinition existingDefinition =
3285             nameFormsByNameOrOID.get(lowerOID);
3286        if (existingDefinition == null)
3287        {
3288          existingDefinition = existingSchema.getNameFormByName(lowerOID);
3289        }
3290
3291        if (existingDefinition != null)
3292        {
3293          errorMessages.add(
3294               ERR_SCHEMA_VALIDATOR_NF_ALREADY_DEFINED_WITH_OID.get(
3295                    nameFormString, schemaFile.getAbsolutePath(),
3296                    existingDefinition.toString()));
3297          isDuplicate = true;
3298        }
3299
3300        if (! isDuplicate)
3301        {
3302          for (final String name : nameForm.getNames())
3303          {
3304            final String lowerName = StaticUtils.toLowerCase(name);
3305            existingDefinition = nameFormsByNameOrOID.get(lowerName);
3306            if (existingDefinition == null)
3307            {
3308              existingDefinition = existingSchema.getNameFormByName(lowerName);
3309            }
3310
3311            if (existingDefinition != null)
3312            {
3313              errorMessages.add(
3314                   ERR_SCHEMA_VALIDATOR_NF_ALREADY_DEFINED_WITH_NAME.get(
3315                        nameFormString, schemaFile.getAbsolutePath(),
3316                        name, existingDefinition.toString()));
3317              isDuplicate = true;
3318              break;
3319            }
3320          }
3321        }
3322
3323        if ((! isDuplicate) && (structuralClass != null))
3324        {
3325          existingDefinition = nameFormsByOC.get(structuralClass);
3326          if (existingDefinition == null)
3327          {
3328            existingDefinition = existingSchema.getNameFormByObjectClass(
3329                 structuralClassNameOrOID);
3330          }
3331
3332          if (existingDefinition != null)
3333          {
3334            errorMessages.add(
3335                 ERR_SCHEMA_VALIDATOR_NF_ALREADY_DEFINED_WITH_OC.get(
3336                      nameFormString, schemaFile.getAbsolutePath(),
3337                      structuralClassNameOrOID, existingDefinition.toString()));
3338            isDuplicate = true;
3339          }
3340        }
3341      }
3342
3343
3344      // Add the name form to the maps so we can detect conflicts with later
3345      // name forms.
3346      if (! isDuplicate)
3347      {
3348        nameFormsByNameOrOID.put(StaticUtils.toLowerCase(nameForm.getOID()),
3349             nameForm);
3350        for (final String name : nameForm.getNames())
3351        {
3352          nameFormsByNameOrOID.put(StaticUtils.toLowerCase(name), nameForm);
3353        }
3354
3355        if (structuralClass != null)
3356        {
3357          nameFormsByOC.put(structuralClass, nameForm);
3358        }
3359      }
3360    }
3361  }
3362
3363
3364
3365  /**
3366   * Validates any DIT content rule definitions contained in the provided schema
3367   * entry.
3368   *
3369   * @param  schemaEntry
3370   *              The entry containing the schema definitions to validate.  It
3371   *              must not be {@code null}.
3372   * @param  schemaFile
3373   *              The file from which the schema entry was read.  It must not be
3374   *              {@code null}.
3375   * @param  dcrMap
3376   *              A map of the DIT content rule definitions that have already
3377   *              been parsed from the same file.  It must not be {@code null}
3378   *              (but may be empty), and it must be updatable.
3379   * @param  existingSchema
3380   *              An existing schema that has already been read (e.g., from
3381   *              earlier schema files).  It must not be {@code null}.
3382   * @param  errorMessages
3383   *              A list that will be updated with error messages about any
3384   *              problems identified during processing.  It must not be
3385   *              {@code null}, and it must be updatable.
3386   */
3387  private void validateDITContentRules(@NotNull final Entry schemaEntry,
3388                    @NotNull final File schemaFile,
3389                    @NotNull final Map<String,DITContentRuleDefinition> dcrMap,
3390                    @NotNull final Schema existingSchema,
3391                    @NotNull final List<String> errorMessages)
3392  {
3393    for (final String dcrString :
3394         schemaEntry.getAttributeValues(Schema.ATTR_DIT_CONTENT_RULE))
3395    {
3396      // If DIT content rules aren't allowed, then report an error without
3397      // doing anything else.
3398      if (! allowedSchemaElementTypes.contains(
3399           SchemaElementType.DIT_CONTENT_RULE))
3400      {
3401        errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_NOT_ALLOWED.get(
3402             schemaFile.getAbsolutePath(), dcrString));
3403        continue;
3404      }
3405
3406
3407      // Make sure that we can parse the DIT content rule definition.
3408      final DITContentRuleDefinition dcr;
3409      try
3410      {
3411        dcr = new DITContentRuleDefinition(dcrString);
3412      }
3413      catch (final LDAPException e)
3414      {
3415        Debug.debugException(e);
3416        errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_DCR.get(
3417             dcrString, schemaFile.getAbsolutePath(), e.getMessage()));
3418        continue;
3419      }
3420
3421
3422      // Make sure that the DIT content rule has a valid numeric OID.
3423      try
3424      {
3425        validateOID(dcr.getOID(), dcr.getNames());
3426      }
3427      catch (final ParseException e)
3428      {
3429        Debug.debugException(e);
3430        errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_INVALID_OID.get(
3431             dcrString, schemaFile.getAbsolutePath(), e.getMessage()));
3432      }
3433
3434
3435      // The DIT content rule's numeric OID must reference a structural
3436      // object class.
3437      final ObjectClassDefinition structuralObjectClass =
3438           existingSchema.getObjectClass(dcr.getOID());
3439      if (structuralObjectClass == null)
3440      {
3441        if (! allowReferencesToUndefinedElementTypes.contains(
3442             SchemaElementType.OBJECT_CLASS))
3443        {
3444          errorMessages.add(
3445               ERR_SCHEMA_VALIDATOR_DCR_UNKNOWN_STRUCTURAL_CLASS.get(dcrString,
3446                    schemaFile.getAbsolutePath(), dcr.getOID()));
3447        }
3448      }
3449      else
3450      {
3451        if ((structuralObjectClass.getObjectClassType() != null) &&
3452             (structuralObjectClass.getObjectClassType() !=
3453                  ObjectClassType.STRUCTURAL))
3454        {
3455          errorMessages.add(
3456               ERR_SCHEMA_VALIDATOR_DCR_STRUCTURAL_CLASS_NOT_STRUCTURAL.get(
3457                    dcrString, schemaFile.getAbsolutePath(), dcr.getOID(),
3458                    structuralObjectClass.toString()));
3459        }
3460      }
3461
3462
3463      // Make sure that all of the names are valid.
3464      if ((dcr.getNames().length == 0) &&
3465           (! allowElementsWithoutNames))
3466      {
3467        errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_NO_NAME.get(
3468             dcrString, schemaFile.getAbsolutePath()));
3469      }
3470
3471      for (final String name : dcr.getNames())
3472      {
3473        try
3474        {
3475          validateName(name);
3476        }
3477        catch (final ParseException e)
3478        {
3479          Debug.debugException(e);
3480          errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_INVALID_NAME.get(
3481               dcrString, schemaFile.getAbsolutePath(), name,
3482               e.getMessage()));
3483        }
3484      }
3485
3486
3487      // If the DIT content rule has a description, then make sure it's not
3488      // empty.
3489      if (! allowEmptyDescription)
3490      {
3491        final String description = dcr.getDescription();
3492        if ((description != null) && description.isEmpty())
3493        {
3494          errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_EMPTY_DESCRIPTION.get(
3495               dcrString, schemaFile.getAbsolutePath()));
3496        }
3497      }
3498
3499
3500      // If the DIT content rule is declared obsolete, then make sure that's
3501      // allowed.
3502      if (dcr.isObsolete() && (! allowObsoleteElements))
3503      {
3504        errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_OBSOLETE.get(
3505             dcrString, schemaFile.getAbsolutePath()));
3506      }
3507
3508
3509      //  If there are any auxiliary classes, make sure they are defined and
3510      // are auxiliary.
3511      for (final String auxClassNameOrOID : dcr.getAuxiliaryClasses())
3512      {
3513        final ObjectClassDefinition auxClass =
3514             existingSchema.getObjectClass(auxClassNameOrOID);
3515        if (auxClass == null)
3516        {
3517          if (! allowReferencesToUndefinedElementTypes.contains(
3518               SchemaElementType.OBJECT_CLASS))
3519          {
3520            errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_UNDEFINED_AUX_CLASS.get(
3521                 dcrString, schemaFile.getAbsolutePath(), auxClassNameOrOID));
3522          }
3523        }
3524        else if (auxClass.getObjectClassType() != ObjectClassType.AUXILIARY)
3525        {
3526          errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_AUX_CLASS_NOT_AUX.get(
3527               dcrString, schemaFile.getAbsolutePath(),
3528               auxClass.toString()));
3529        }
3530      }
3531
3532
3533      // If there are any required attribute types, then make sure they are
3534      // defined.
3535      final Set<String> requiredAttrNamesAndOIDs = new HashSet<>();
3536      for (final String attrNameOrOID : dcr.getRequiredAttributes())
3537      {
3538        requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(attrNameOrOID));
3539
3540        final AttributeTypeDefinition at =
3541             existingSchema.getAttributeType(attrNameOrOID);
3542        if (at == null)
3543        {
3544          if (! allowReferencesToUndefinedElementTypes.contains(
3545               SchemaElementType.ATTRIBUTE_TYPE))
3546          {
3547            errorMessages.add(
3548                 ERR_SCHEMA_VALIDATOR_DCR_UNDEFINED_REQUIRED_ATTR.get(
3549                      dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
3550          }
3551        }
3552        else
3553        {
3554          requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(at.getOID()));
3555          for (final String name : at.getNames())
3556          {
3557            requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(name));
3558          }
3559        }
3560      }
3561
3562
3563      // If there are any optional attribute types, then make sure they are
3564      // defined.  Also, make sure they are also not listed as required.
3565      final Set<String> optionalAttrNamesAndOIDs = new HashSet<>();
3566      for (final String attrNameOrOID : dcr.getOptionalAttributes())
3567      {
3568        final String lowerNameOrOID = StaticUtils.toLowerCase(attrNameOrOID);
3569        optionalAttrNamesAndOIDs.add(lowerNameOrOID);
3570
3571        if (requiredAttrNamesAndOIDs.contains(lowerNameOrOID))
3572        {
3573          errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_ATTR_REQ_AND_OPT.get(
3574               dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
3575        }
3576
3577        final AttributeTypeDefinition at =
3578             existingSchema.getAttributeType(lowerNameOrOID);
3579        if (at == null)
3580        {
3581          if (! allowReferencesToUndefinedElementTypes.contains(
3582               SchemaElementType.ATTRIBUTE_TYPE))
3583          {
3584            errorMessages.add(
3585                 ERR_SCHEMA_VALIDATOR_DCR_UNDEFINED_OPTIONAL_ATTR.get(
3586                      dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
3587          }
3588        }
3589        else
3590        {
3591          optionalAttrNamesAndOIDs.add(StaticUtils.toLowerCase(at.getOID()));
3592          for (final String name : at.getNames())
3593          {
3594            optionalAttrNamesAndOIDs.add(StaticUtils.toLowerCase(name));
3595          }
3596        }
3597      }
3598
3599
3600      // If there are any prohibited attribute types, then make sure they are
3601      // defined.  Also, make sure they are not listed as required or optional,
3602      // and make sure they are not required by the structural or any of the
3603      // auxiliary classes.
3604      for (final String attrNameOrOID : dcr.getProhibitedAttributes())
3605      {
3606        final String lowerNameOrOID = StaticUtils.toLowerCase(attrNameOrOID);
3607        if (requiredAttrNamesAndOIDs.contains(lowerNameOrOID))
3608        {
3609          errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_ATTR_REQ_AND_NOT.get(
3610               dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
3611        }
3612
3613        if (optionalAttrNamesAndOIDs.contains(lowerNameOrOID))
3614        {
3615          errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_ATTR_OPT_AND_NOT.get(
3616               dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
3617        }
3618
3619        final AttributeTypeDefinition at =
3620             existingSchema.getAttributeType(lowerNameOrOID);
3621        if (at == null)
3622        {
3623          if (! allowReferencesToUndefinedElementTypes.contains(
3624               SchemaElementType.ATTRIBUTE_TYPE))
3625          {
3626            errorMessages.add(
3627                 ERR_SCHEMA_VALIDATOR_DCR_UNDEFINED_PROHIBITED_ATTR.get(
3628                      dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
3629          }
3630        }
3631        else
3632        {
3633          if (structuralObjectClass != null)
3634          {
3635            if (structuralObjectClass.getRequiredAttributes(existingSchema,
3636                 true).contains(at))
3637            {
3638              errorMessages.add(
3639                   ERR_SCHEMA_VALIDATOR_DCR_PROHIBITED_ATTR_REQUIRED_BY_STRUCT.
3640                        get(dcrString, schemaFile.getAbsolutePath(),
3641                             attrNameOrOID));
3642            }
3643          }
3644
3645          for (final String auxClassNameOrOID : dcr.getAuxiliaryClasses())
3646          {
3647            final ObjectClassDefinition auxClass =
3648                 existingSchema.getObjectClass(auxClassNameOrOID);
3649            if ((auxClass != null) &&
3650                 auxClass.getRequiredAttributes(existingSchema, true).contains(
3651                      at))
3652            {
3653              errorMessages.add(
3654                   ERR_SCHEMA_VALIDATOR_DCR_PROHIBITED_ATTR_REQUIRED_BY_AUX.
3655                        get(dcrString, schemaFile.getAbsolutePath(),
3656                             attrNameOrOID, auxClassNameOrOID));
3657            }
3658          }
3659        }
3660      }
3661
3662
3663      // Make sure that the DIT content rule isn't already defined.
3664      boolean isDuplicate = false;
3665      if (! allowRedefiningElements)
3666      {
3667        final String lowerOID = StaticUtils.toLowerCase(dcr.getOID());
3668        DITContentRuleDefinition existingDefinition = dcrMap.get(lowerOID);
3669        if (existingDefinition == null)
3670        {
3671          existingDefinition = existingSchema.getDITContentRule(lowerOID);
3672        }
3673
3674        if (existingDefinition != null)
3675        {
3676          errorMessages.add(
3677               ERR_SCHEMA_VALIDATOR_DCR_ALREADY_DEFINED_WITH_OID.get(
3678                    dcrString, schemaFile.getAbsolutePath(),
3679                    existingDefinition.toString()));
3680          isDuplicate = true;
3681        }
3682
3683        if (! isDuplicate)
3684        {
3685          for (final String name : dcr.getNames())
3686          {
3687            final String lowerName = StaticUtils.toLowerCase(name);
3688            existingDefinition = dcrMap.get(lowerName);
3689            if (existingDefinition == null)
3690            {
3691              existingDefinition = existingSchema.getDITContentRule(lowerName);
3692            }
3693
3694            if (existingDefinition != null)
3695            {
3696              errorMessages.add(
3697                   ERR_SCHEMA_VALIDATOR_DCR_ALREADY_DEFINED_WITH_NAME.get(
3698                        dcrString, schemaFile.getAbsolutePath(),
3699                        name, existingDefinition.toString()));
3700              isDuplicate = true;
3701              break;
3702            }
3703          }
3704        }
3705      }
3706
3707
3708      // Add the DIT content rule to the map so it can be used to detect
3709      // duplicates.
3710      if (! isDuplicate)
3711      {
3712        dcrMap.put(StaticUtils.toLowerCase(dcr.getOID()), dcr);
3713        for (final String name : dcr.getNames())
3714        {
3715          dcrMap.put(StaticUtils.toLowerCase(name), dcr);
3716        }
3717      }
3718    }
3719  }
3720
3721
3722
3723  /**
3724   * Validates any DIT structure rule definitions contained in the provided
3725   * schema entry.
3726   *
3727   * @param  schemaEntry
3728   *              The entry containing the schema definitions to validate.  It
3729   *              must not be {@code null}.
3730   * @param  schemaFile
3731   *              The file from which the schema entry was read.  It must not be
3732   *              {@code null}.
3733   * @param  dsrIDAndNameMap
3734   *              A map of the DIT structure rule definitions that have already
3735   *              been parsed from the same file, indexed by rule ID and name.
3736   *              It must not be {@code null} (but may be empty), and it must be
3737   *              updatable.
3738   * @param  dsrNFMap
3739   *              A map of the DIT structure rule definitions that have already
3740   *              been parsed from the same file, indexed by name form
3741   *              definition.  It must not be {@code null} (but may be empty),
3742   *              and it must be updatable.
3743   * @param  existingSchema
3744   *              An existing schema that has already been read (e.g., from
3745   *              earlier schema files).  It must not be {@code null}.
3746   * @param  errorMessages
3747   *              A list that will be updated with error messages about any
3748   *              problems identified during processing.  It must not be
3749   *              {@code null}, and it must be updatable.
3750   */
3751  private void validateDITStructureRules(@NotNull final Entry schemaEntry,
3752       @NotNull final File schemaFile,
3753       @NotNull final Map<String,DITStructureRuleDefinition> dsrIDAndNameMap,
3754       @NotNull final Map<NameFormDefinition,DITStructureRuleDefinition>
3755            dsrNFMap,
3756       @NotNull final Schema existingSchema,
3757       @NotNull final List<String> errorMessages)
3758  {
3759    for (final String dsrString :
3760         schemaEntry.getAttributeValues(Schema.ATTR_DIT_STRUCTURE_RULE))
3761    {
3762      // If DIT structure rules aren't allowed, then report an error without
3763      // doing anything else.
3764      if (! allowedSchemaElementTypes.contains(
3765           SchemaElementType.DIT_STRUCTURE_RULE))
3766      {
3767        errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_NOT_ALLOWED.get(
3768             schemaFile.getAbsolutePath(), dsrString));
3769        continue;
3770      }
3771
3772
3773      // Make sure that we can parse the DIT structure rule definition.
3774      final DITStructureRuleDefinition dsr;
3775      try
3776      {
3777        dsr = new DITStructureRuleDefinition(dsrString);
3778      }
3779      catch (final LDAPException e)
3780      {
3781        Debug.debugException(e);
3782        errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_DSR.get(
3783             dsrString, schemaFile.getAbsolutePath(), e.getMessage()));
3784        continue;
3785      }
3786
3787
3788      // Make sure that all of the names are valid.
3789      if ((dsr.getNames().length == 0) &&
3790           (! allowElementsWithoutNames))
3791      {
3792        errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_NO_NAME.get(
3793             dsrString, schemaFile.getAbsolutePath()));
3794      }
3795
3796      for (final String name : dsr.getNames())
3797      {
3798        try
3799        {
3800          validateName(name);
3801        }
3802        catch (final ParseException e)
3803        {
3804          Debug.debugException(e);
3805          errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_INVALID_NAME.get(
3806               dsrString, schemaFile.getAbsolutePath(), name,
3807               e.getMessage()));
3808        }
3809      }
3810
3811
3812      // If the DIT structure rule has a description, then make sure it's not
3813      // empty.
3814      if (! allowEmptyDescription)
3815      {
3816        final String description = dsr.getDescription();
3817        if ((description != null) && description.isEmpty())
3818        {
3819          errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_EMPTY_DESCRIPTION.get(
3820               dsrString, schemaFile.getAbsolutePath()));
3821        }
3822      }
3823
3824
3825      // If the DIT content rule is declared obsolete, then make sure that's
3826      // allowed.
3827      if (dsr.isObsolete() && (! allowObsoleteElements))
3828      {
3829        errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_OBSOLETE.get(
3830             dsrString, schemaFile.getAbsolutePath()));
3831      }
3832
3833
3834      // Make sure that the name form is defined.
3835      final String nameFormNameOrOID = dsr.getNameFormID();
3836      final NameFormDefinition nameForm =
3837           existingSchema.getNameFormByName(nameFormNameOrOID);
3838      if ((nameForm == null) &&
3839           (! allowReferencesToUndefinedElementTypes.contains(
3840                SchemaElementType.NAME_FORM)))
3841      {
3842        errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_UNDEFINED_NF.get(dsrString,
3843             schemaFile.getAbsolutePath(), nameFormNameOrOID));
3844      }
3845
3846
3847      // If there are any superior rule IDs, then make sure they are defined.
3848      if (! allowReferencesToUndefinedElementTypes.contains(
3849           SchemaElementType.DIT_STRUCTURE_RULE))
3850      {
3851        for (final int superiorRuleID : dsr.getSuperiorRuleIDs())
3852        {
3853          final DITStructureRuleDefinition superiorDSR =
3854               dsrIDAndNameMap.get(String.valueOf(superiorRuleID));
3855          if (superiorDSR == null)
3856          {
3857            errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_UNDEFINED_SUP.get(
3858                 dsrString, schemaFile.getAbsolutePath(), superiorRuleID));
3859          }
3860        }
3861      }
3862
3863
3864      // Make sure that the DIT structure rule isn't already defined.
3865      boolean isDuplicate = false;
3866      if (! allowRedefiningElements)
3867      {
3868        DITStructureRuleDefinition existingDefinition =
3869             dsrIDAndNameMap.get(String.valueOf(dsr.getRuleID()));
3870        if (existingDefinition == null)
3871        {
3872          existingDefinition =
3873               existingSchema.getDITStructureRuleByID(dsr.getRuleID());
3874        }
3875
3876        if (existingDefinition != null)
3877        {
3878          errorMessages.add(
3879               ERR_SCHEMA_VALIDATOR_DSR_ALREADY_DEFINED_WITH_ID.get(
3880                    dsrString, schemaFile.getAbsolutePath(),
3881                    existingDefinition.toString()));
3882          isDuplicate = true;
3883        }
3884
3885        if (! isDuplicate)
3886        {
3887          for (final String name : dsr.getNames())
3888          {
3889            final String lowerName = StaticUtils.toLowerCase(name);
3890            existingDefinition = dsrIDAndNameMap.get(lowerName);
3891            if (existingDefinition == null)
3892            {
3893              existingDefinition =
3894                   existingSchema.getDITStructureRuleByName(lowerName);
3895            }
3896
3897            if (existingDefinition != null)
3898            {
3899              errorMessages.add(
3900                   ERR_SCHEMA_VALIDATOR_DSR_ALREADY_DEFINED_WITH_NAME.get(
3901                        dsrString, schemaFile.getAbsolutePath(),
3902                        name, existingDefinition.toString()));
3903              isDuplicate = true;
3904              break;
3905            }
3906          }
3907        }
3908
3909        if ((! isDuplicate) && (nameForm != null))
3910        {
3911          existingDefinition = dsrNFMap.get(nameForm);
3912          if (existingDefinition == null)
3913          {
3914            existingDefinition = existingSchema.getDITStructureRuleByNameForm(
3915                 nameFormNameOrOID);
3916          }
3917
3918          if (existingDefinition != null)
3919          {
3920            errorMessages.add(
3921                 ERR_SCHEMA_VALIDATOR_DSR_ALREADY_DEFINED_WITH_NF.get(
3922                      dsrString, schemaFile.getAbsolutePath(),
3923                      nameFormNameOrOID, existingDefinition.toString()));
3924            isDuplicate = true;
3925          }
3926        }
3927      }
3928
3929
3930      // Add the DIT content rule to the map so it can be used to detect
3931      // duplicates.
3932      if (! isDuplicate)
3933      {
3934        dsrIDAndNameMap.put(String.valueOf(dsr.getRuleID()), dsr);
3935        for (final String name : dsr.getNames())
3936        {
3937          dsrIDAndNameMap.put(StaticUtils.toLowerCase(name), dsr);
3938        }
3939
3940        if (nameForm != null)
3941        {
3942          dsrNFMap.put(nameForm, dsr);
3943        }
3944      }
3945    }
3946  }
3947
3948
3949
3950  /**
3951   * Validates any matching rule use definitions contained in the provided
3952   * schema entry.
3953   *
3954   * @param  schemaEntry
3955   *              The entry containing the schema definitions to validate.  It
3956   *              must not be {@code null}.
3957   * @param  schemaFile
3958   *              The file from which the schema entry was read.  It must not be
3959   *              {@code null}.
3960   * @param  mruMap
3961   *              A map of the matching rule use definitions that have already
3962   *              been parsed from the same file.  It must not be {@code null}
3963   *              (but may be empty), and it must be updatable.
3964   * @param  existingSchema
3965   *              An existing schema that has already been read (e.g., from
3966   *              earlier schema files).  It must not be {@code null}.
3967   * @param  errorMessages
3968   *              A list that will be updated with error messages about any
3969   *              problems identified during processing.  It must not be
3970   *              {@code null}, and it must be updatable.
3971   */
3972  private void validateMatchingRuleUses(@NotNull final Entry schemaEntry,
3973                    @NotNull final File schemaFile,
3974                    @NotNull final Map<String,MatchingRuleUseDefinition> mruMap,
3975                    @NotNull final Schema existingSchema,
3976                    @NotNull final List<String> errorMessages)
3977  {
3978    for (final String mruString :
3979         schemaEntry.getAttributeValues(Schema.ATTR_MATCHING_RULE_USE))
3980    {
3981      // If matching rule uses aren't allowed, then report an error without
3982      // doing anything else.
3983      if (! allowedSchemaElementTypes.contains(
3984           SchemaElementType.MATCHING_RULE_USE))
3985      {
3986        errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_NOT_ALLOWED.get(
3987             schemaFile.getAbsolutePath(), mruString));
3988        continue;
3989      }
3990
3991
3992      // Make sure that we can parse the matching rule use definition.
3993      final MatchingRuleUseDefinition mru;
3994      try
3995      {
3996        mru = new MatchingRuleUseDefinition(mruString);
3997      }
3998      catch (final LDAPException e)
3999      {
4000        Debug.debugException(e);
4001        errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_MRU.get(
4002             mruString, schemaFile.getAbsolutePath(), e.getMessage()));
4003        continue;
4004      }
4005
4006
4007      // Make sure that the matching rule use has a valid numeric OID.
4008      try
4009      {
4010        validateOID(mru.getOID(), mru.getNames());
4011      }
4012      catch (final ParseException e)
4013      {
4014        Debug.debugException(e);
4015        errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_INVALID_OID.get(
4016             mruString, schemaFile.getAbsolutePath(), e.getMessage()));
4017      }
4018
4019
4020      // Make sure that the matching rule use OID references a defined matching
4021      // rule.
4022      MatchingRuleDefinition matchingRule =
4023           matchingRuleMap.get( StaticUtils.toLowerCase(mru.getOID()));
4024      if (matchingRule == null)
4025      {
4026        matchingRule = existingSchema.getMatchingRule(mru.getOID());
4027      }
4028
4029      if ((matchingRule == null) &&
4030           (! allowReferencesToUndefinedElementTypes.contains(
4031                SchemaElementType.MATCHING_RULE)))
4032      {
4033        errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_UNDEFINED_MR.get(
4034             mruString, schemaFile.getAbsolutePath(), mru.getOID()));
4035      }
4036
4037
4038      // Make sure that all of the names are valid.
4039      if ((mru.getNames().length == 0) &&
4040           (! allowElementsWithoutNames))
4041      {
4042        errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_NO_NAME.get(
4043             mruString, schemaFile.getAbsolutePath()));
4044      }
4045
4046      for (final String name : mru.getNames())
4047      {
4048        try
4049        {
4050          validateName(name);
4051        }
4052        catch (final ParseException e)
4053        {
4054          Debug.debugException(e);
4055          errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_INVALID_NAME.get(
4056               mruString, schemaFile.getAbsolutePath(), name,
4057               e.getMessage()));
4058        }
4059      }
4060
4061
4062      // If the matching rule use has a description, then make sure it's not
4063      // empty.
4064      if (! allowEmptyDescription)
4065      {
4066        final String description = mru.getDescription();
4067        if ((description != null) && description.isEmpty())
4068        {
4069          errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_EMPTY_DESCRIPTION.get(
4070               mruString, schemaFile.getAbsolutePath()));
4071        }
4072      }
4073
4074
4075      // If the matching rule use is declared obsolete, then make sure that's
4076      // allowed.
4077      if (mru.isObsolete() && (! allowObsoleteElements))
4078      {
4079        errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_OBSOLETE.get(
4080             mruString, schemaFile.getAbsolutePath()));
4081      }
4082
4083
4084      // Make sure that all of the referenced attribute types are defined in the
4085      // schema.
4086      final Set<AttributeTypeDefinition> applicableTypes = new HashSet<>();
4087      for (final String attrNameOrOID : mru.getApplicableAttributeTypes())
4088      {
4089        final AttributeTypeDefinition at =
4090             existingSchema.getAttributeType(attrNameOrOID);
4091        if (at == null)
4092        {
4093          if (! allowReferencesToUndefinedElementTypes.contains(
4094               SchemaElementType.ATTRIBUTE_TYPE))
4095          {
4096            errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_UNDEFINED_AT.get(
4097                 mruString, schemaFile.getAbsolutePath(), attrNameOrOID));
4098          }
4099        }
4100        else
4101        {
4102          applicableTypes.add(at);
4103        }
4104      }
4105
4106
4107      // Examine the schema to determine whether there are any attribute types
4108      // that use the associated matching rule but aren't in the list of
4109      // applicable types.
4110      if (matchingRule != null)
4111      {
4112        for (final AttributeTypeDefinition at :
4113             existingSchema.getAttributeTypes())
4114        {
4115          if (applicableTypes.contains(at))
4116          {
4117            continue;
4118          }
4119
4120          final String eqMR = at.getEqualityMatchingRule();
4121          if ((eqMR != null) && matchingRule.hasNameOrOID(eqMR))
4122          {
4123            errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_PROHIBITS_AT_EQ.get(
4124                 at.toString(), eqMR, mruString, schemaFile.getAbsolutePath()));
4125          }
4126
4127          final String ordMR = at.getOrderingMatchingRule();
4128          if ((ordMR != null) && matchingRule.hasNameOrOID(ordMR))
4129          {
4130            errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_PROHIBITS_AT_ORD.get(
4131                 at.toString(), ordMR, mruString,
4132                 schemaFile.getAbsolutePath()));
4133          }
4134
4135          final String subMR = at.getSubstringMatchingRule();
4136          if ((subMR != null) && matchingRule.hasNameOrOID(subMR))
4137          {
4138            errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_PROHIBITS_AT_SUB.get(
4139                 at.toString(), subMR, mruString,
4140                 schemaFile.getAbsolutePath()));
4141          }
4142        }
4143      }
4144
4145
4146      // Make sure that the matching rule use isn't already defined.
4147      boolean isDuplicate = false;
4148      if (! allowRedefiningElements)
4149      {
4150        final String lowerOID = StaticUtils.toLowerCase(mru.getOID());
4151        MatchingRuleUseDefinition existingDefinition = mruMap.get(lowerOID);
4152        if (existingDefinition == null)
4153        {
4154          existingDefinition = existingSchema.getMatchingRuleUse(lowerOID);
4155        }
4156
4157        if (existingDefinition != null)
4158        {
4159          errorMessages.add(
4160               ERR_SCHEMA_VALIDATOR_MRU_ALREADY_DEFINED_WITH_OID.get(
4161                    mruString, schemaFile.getAbsolutePath(),
4162                    existingDefinition.toString()));
4163          isDuplicate = true;
4164        }
4165
4166        if (! isDuplicate)
4167        {
4168          for (final String name : mru.getNames())
4169          {
4170            final String lowerName = StaticUtils.toLowerCase(name);
4171            existingDefinition = mruMap.get(lowerName);
4172            if (existingDefinition == null)
4173            {
4174              existingDefinition = existingSchema.getMatchingRuleUse(lowerName);
4175            }
4176
4177            if (existingDefinition != null)
4178            {
4179              errorMessages.add(
4180                   ERR_SCHEMA_VALIDATOR_MRU_ALREADY_DEFINED_WITH_NAME.get(
4181                        mruString, schemaFile.getAbsolutePath(),
4182                        name, existingDefinition.toString()));
4183              isDuplicate = true;
4184              break;
4185            }
4186          }
4187        }
4188      }
4189
4190
4191      // Add the matching rule use to the map so it can be used to detect
4192      // duplicates.
4193      if (! isDuplicate)
4194      {
4195        mruMap.put(StaticUtils.toLowerCase(mru.getOID()), mru);
4196        for (final String name : mru.getNames())
4197        {
4198          mruMap.put(StaticUtils.toLowerCase(name), mru);
4199        }
4200      }
4201    }
4202  }
4203
4204
4205
4206  /**
4207   * Ensures that the provided object identifier is valid, within the
4208   * constraints of the schema validator.
4209   *
4210   * @param  oid    The object identifier to validate.  It must not be
4211   *                {@code null}.
4212   * @param  names  The set of names for the schema element.  It may be
4213   *                {@code null} or empty if the element does not have any
4214   *                names.
4215   *
4216   * @throws  ParseException  If the provided OID is not valid.
4217   */
4218  private void validateOID(@NotNull final String oid,
4219                           @Nullable final String[] names)
4220       throws ParseException
4221  {
4222    try
4223    {
4224      OID.parseNumericOID(oid, useStrictOIDValidation);
4225    }
4226    catch (final ParseException e)
4227    {
4228      Debug.debugException(e);
4229
4230      boolean acceptable = false;
4231      if (allowNonNumericOIDsUsingName && (names != null))
4232      {
4233        for (final String name : names)
4234        {
4235          if (oid.equalsIgnoreCase(name + "-oid"))
4236          {
4237            acceptable = true;
4238            break;
4239          }
4240        }
4241      }
4242
4243      if ((! acceptable ) && allowNonNumericOIDsNotUsingName)
4244      {
4245        acceptable = true;
4246      }
4247
4248      if (! acceptable)
4249      {
4250        throw e;
4251      }
4252    }
4253  }
4254
4255
4256
4257  /**
4258   * Ensures that the provided name is valid for a schema element, within the
4259   * constraints of the schema validator.
4260   *
4261   * @param  name  The name to validate.  It must not be {@code null}.
4262   *
4263   * @throws  ParseException  If the provided name is not valid.
4264   */
4265  void validateName(@NotNull final String name)
4266       throws ParseException
4267  {
4268    if (name.isEmpty())
4269    {
4270      throw new ParseException(ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_EMPTY.get(),
4271           0);
4272    }
4273
4274    final char firstChar = name.charAt(0);
4275    if (((firstChar >= 'a') && (firstChar <= 'z')) ||
4276         ((firstChar >= 'A') && (firstChar <= 'Z')))
4277    {
4278      // This is always okay.
4279    }
4280    else if ((firstChar >= '0') && (firstChar <= '9'))
4281    {
4282      if (allowNamesWithInitialDigit)
4283      {
4284        // This is technically illegal, but we'll allow it.
4285      }
4286      else
4287      {
4288        throw new ParseException(
4289             ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_DOES_NOT_START_WITH_LETTER.get(),
4290             0);
4291      }
4292    }
4293    else if (firstChar == '-')
4294    {
4295      if (allowNamesWithInitialHyphen)
4296      {
4297        // This is technically illegal, but we'll allow it.
4298      }
4299      else
4300      {
4301        throw new ParseException(
4302             ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_DOES_NOT_START_WITH_LETTER.get(),
4303             0);
4304      }
4305    }
4306    else if (firstChar == '_')
4307    {
4308      if (allowNamesWithUnderscore)
4309      {
4310        // This is technically illegal, but we'll allow it.
4311      }
4312      else
4313      {
4314        throw new ParseException(
4315             ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_DOES_NOT_START_WITH_LETTER.get(),
4316             0);
4317      }
4318    }
4319    else
4320    {
4321      throw new ParseException(
4322           ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_DOES_NOT_START_WITH_LETTER.get(),
4323           0);
4324    }
4325
4326    for (int i = 1; i < name.length(); i++)
4327    {
4328      final char subsequentChar = name.charAt(i);
4329      if (((subsequentChar >= 'a') && (subsequentChar <= 'z')) ||
4330           ((subsequentChar >= 'A') && (subsequentChar <= 'Z')) ||
4331           ((subsequentChar >= '0') && (subsequentChar <= '9')) ||
4332           (subsequentChar == '-'))
4333      {
4334        // This is always okay.
4335      }
4336      else if ((subsequentChar == '_') && allowNamesWithUnderscore)
4337      {
4338        // This is technically illegal, but we'll allow it.
4339      }
4340      else
4341      {
4342        throw new ParseException(
4343             ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_ILLEGAL_CHARACTER.get(
4344                  subsequentChar, i),
4345             i);
4346      }
4347    }
4348  }
4349}