com.unboundid.ldap.sdk.schema.SchemaValidator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unboundid-ldapsdk Show documentation
Show all versions of unboundid-ldapsdk Show documentation
The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use
Java API for communicating with LDAP directory servers and performing
related tasks like reading and writing LDIF, encoding and decoding data
using base64 and ASN.1 BER, and performing secure communication. This
package contains the Standard Edition of the LDAP SDK, which is a
complete, general-purpose library for communicating with LDAPv3 directory
servers.
/*
* Copyright 2020-2021 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2020-2021 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2020-2021 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk.schema;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.InternalSDKHelper;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldif.LDIFException;
import com.unboundid.ldif.LDIFReader;
import com.unboundid.util.Debug;
import com.unboundid.util.Mutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.OID;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;
import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
/**
* This class provides a mechanism form validating definitions in an LDAP
* schema. Schema elements are expected to be read from one or more LDIF files
* containing subschema subentries as described in
* RFC 4512 section 4.2, with
* elements defined using the syntaxes described in section 4.1 of the same
* specification.
*
* This schema validator can perform the following checks:
*
* - It ensures that each schema element can be parsed.
* - It ensures that element names and OIDs are properly formed, optionally
* allowing for more lax validation that some servers may permit.
* - It ensures that each schema element does not reference any undefined
* schema elements.
* - It can verify that each element is only defined once.
* - It can optionally determine whether definitions may use functionality
* that some servers do not support.
* - It can verify that schema entries are valid in accordance with the
* schema it contains.
* - It can optionally ensure that schema files are named using an
* expected pattern.
* - It can optionally validate extensions used in schema elements.
*
*
* It ensures that all definitions can be parsed, contain valid
* content, do not reference any undefined schema elements, etc.
*/
@Mutable()
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public final class SchemaValidator
{
/**
* Indicates whether an instance of the Ping Identity Directory Server is
* available.
*/
static final boolean PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE;
/**
* The path to the schema directory for the available Ping Identity Directory
* Server instance.
*/
@Nullable static final File PING_IDENTITY_DIRECTORY_SERVER_SCHEMA_DIR;
static
{
boolean pingIdentityDSAvailable = false;
File schemaDir = null;
try
{
final File instanceRoot = InternalSDKHelper.getPingIdentityServerRoot();
if (instanceRoot != null)
{
final File instanceRootSchemaDir =
StaticUtils.constructPath(instanceRoot, "config", "schema");
if (new File(instanceRootSchemaDir, "00-core.ldif").exists())
{
// Try to see if we can load the server's schema class. If so, then
// we'll assume that we are running with access to a Ping Identity
// Directory Server, and we'll tailor the defaults accordingly.
// If this fails, we'll just go with the defaults.
Class.forName("com.unboundid.directory.server.types.Schema");
pingIdentityDSAvailable = true;
schemaDir = instanceRootSchemaDir;
}
}
}
catch (final Throwable t)
{
// This is fine. We're just not running with access to a Ping Identity
// Directory Server.
}
PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE = pingIdentityDSAvailable;
PING_IDENTITY_DIRECTORY_SERVER_SCHEMA_DIR = schemaDir;
}
// Indicates whether to allow attribute type definitions that do not include
// an equality matching rule and do not include a superior type.
private boolean allowAttributeTypesWithoutEqualityMatchingRule;
// Indicates whether to allow attribute type definitions that do not include
// an attribute syntax OID and do not include a superior type.
private boolean allowAttributeTypesWithoutSyntax;
// Indicates whether to allow attribute type definitions that are defined with
// the COLLECTIVE indicator.
private boolean allowCollectiveAttributes;
// Indicates whether to allow schema elements that do not have names.
private boolean allowElementsWithoutNames;
// Indicates whether to allow schema elements to contain an empty DESC
// component.
private boolean allowEmptyDescription;
// Indicates whether to allow invalid object class inheritance.
private boolean allowInvalidObjectClassInheritance;
// Indicates whether to allow a single schema file to contain multiple
// entries.
private boolean allowMultipleEntriesPerFile;
// Indicates whether to allow object classes to contain multiple superior
// classes.
private boolean allowMultipleSuperiorObjectClasses;
// Indicates whether to allow LDAP names to start with a digit.
private boolean allowNamesWithInitialDigit;
// Indicates whether to allow LDAP names to start with a hyphen.
private boolean allowNamesWithInitialHyphen;
// Indicates whether to allow LDAP names to contain the underscore character.
private boolean allowNamesWithUnderscore;
// Indicates whether to allow schema elements to contain non-numeric OIDs that
// are not of the form "name-oid".
private boolean allowNonNumericOIDsNotUsingName;
// Indicates whether to allow schema elements to contain non-numeric OIDs that
// are of the form "name-oid".
private boolean allowNonNumericOIDsUsingName;
// Indicates whether to allow attribute type definitions that are defined with
// the OBSOLETE indicator.
private boolean allowObsoleteElements;
// Indicates whether to allow multiple definitions for the same schema
// element.
private boolean allowRedefiningElements;
// Indicates whether to support a schema directory with schema files in
// subdirectories.
private boolean allowSchemaFilesInSubDirectories;
// Indicates whether to allow structural object classes that do not define a
// superior class.
private boolean allowStructuralObjectClassWithoutSuperior;
// Indicates whether to ensure that the entries containing schema definitions
// are themselves valid in accordance with the schema elements that have been
// read.
private boolean ensureSchemaEntryIsValid;
// Indicates whether to ignore files contained in a schema directory that do
// not match the configured file name pattern.
private boolean ignoreSchemaFilesNotMatchingFileNamePattern;
// Indicates whether to use strict validation when examining numeric OIDs.
private boolean useStrictOIDValidation;
// A list of attribute syntax definitions to use when validating attribute
// type definitions.
@NotNull private List attributeSyntaxList;
// A list of matching rule definitions to use when validating attribute type
// definitions.
@NotNull private List matchingRuleList;
// A map of attribute syntax definitions to use when validating attribute type
// definitions.
@NotNull private Map attributeSyntaxMap;
// A map of matching rule definitions to use when validating attribute type
// definitions.
@NotNull private Map matchingRuleMap;
// The pattern that files containing schema definitions are expected to match.
@Nullable private Pattern schemaFileNamePattern;
// The set of schema element types that are allowed to be present in schema
// files.
@NotNull private final Set allowedSchemaElementTypes;
// The set of schema element types that other elements may be allowed to
// reference without the referenced type being defined.
@NotNull private final Set
allowReferencesToUndefinedElementTypes;
/**
* Creates a new schema validator with the default settings.
*/
public SchemaValidator()
{
allowAttributeTypesWithoutEqualityMatchingRule = true;
allowAttributeTypesWithoutSyntax = PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE;
allowCollectiveAttributes = (! PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE);
allowElementsWithoutNames = true;
allowEmptyDescription = false;
allowInvalidObjectClassInheritance = false;
allowMultipleEntriesPerFile = false;
allowMultipleSuperiorObjectClasses =
(! PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE);
allowNamesWithInitialDigit = false;
allowNamesWithInitialHyphen = false;
allowNamesWithUnderscore = false;
allowNonNumericOIDsNotUsingName = false;
allowNonNumericOIDsUsingName = false;
allowObsoleteElements = true;
allowRedefiningElements = false;
allowSchemaFilesInSubDirectories = false;
allowStructuralObjectClassWithoutSuperior = false;
ensureSchemaEntryIsValid = true;
ignoreSchemaFilesNotMatchingFileNamePattern =
(! PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE);
useStrictOIDValidation = true;
attributeSyntaxMap = new LinkedHashMap<>();
attributeSyntaxList = new ArrayList<>();
matchingRuleMap = new LinkedHashMap<>();
matchingRuleList = new ArrayList<>();
schemaFileNamePattern = null;
allowedSchemaElementTypes = EnumSet.allOf(SchemaElementType.class);
allowReferencesToUndefinedElementTypes =
EnumSet.noneOf(SchemaElementType.class);
if (PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE)
{
configureLDAPSDKDefaultAttributeSyntaxes();
configureLDAPSDKDefaultMatchingRules();
schemaFileNamePattern = Pattern.compile("^\\d\\d-.+\\.ldif$");
}
}
/**
* Retrieves the pattern that schema file names are expected to match. By
* default, no schema file name pattern is defined, so there are no
* restrictions on the name that a schema file may have.
*
* @return The pattern that schema file names are expected to match, or
* {@code null} if no schema file name pattern is defined.
*/
@Nullable()
public Pattern getSchemaFileNamePattern()
{
return schemaFileNamePattern;
}
/**
* Indicates whether to ignore any files in a schema directory that do not
* match the value pattern (if one is defined). By default, if a file name
* pattern is defined, then any files whose names do not match that pattern
* will be ignored.
*
* @return {@code true} if files not matching the defined value pattern
* should be ignored, or {@code false} if they should not be ignored
* but a warning should be generated.
*/
public boolean ignoreSchemaFilesNotMatchingFileNamePattern()
{
return ignoreSchemaFilesNotMatchingFileNamePattern;
}
/**
* Specifies a pattern that may be used to indicate which files should be
* examined if a provided path is a directory rather than a file.
*
* @param schemaFileNamePattern
* A regular expression that may be used to specify the pattern
* that schema file names are expected to match. This may be
* {@code null} if no pattern is defined and any identified files
* will be processed.
* @param ignoreSchemaFilesNotMatchingFileNamePattern
* Specifies whether to ignore any files in a schema directory
* that do not match the value pattern (if one is defined). This
* will only have an effect when attempting to parse schema
* definitions from a path that references a directory rather
* than a file. If this is {@code true} then any files that do
* not match the pattern (if it is non-{@code null} will be
* skipped. If this is {@code false}, then all files will be
* parsed even if they do not match the value pattern, but a
* warning will be added to the message list for each file that
* does not match the pattern. If the path provided to the
* {@link #validateSchema} method refers to a file rather than a
* directory, then the file will always be processed, but a
* warning message will be added to the given list if the name
* does not match the given pattern.
*/
public void setSchemaFileNamePattern(
@Nullable final Pattern schemaFileNamePattern,
final boolean ignoreSchemaFilesNotMatchingFileNamePattern)
{
this.schemaFileNamePattern = schemaFileNamePattern;
this.ignoreSchemaFilesNotMatchingFileNamePattern =
ignoreSchemaFilesNotMatchingFileNamePattern;
}
/**
* Indicates whether a schema file is allowed to contain multiple entries.
* By default, each schema file is expected to have exactly one entry.
*
* @return {@code true} if a schema file may have multiple entries and all
* entries contained in that file should be processed, or
* {@code false} if a schema file should only have a single entry
* and additional entries will be ignored and an error message
* reported.
*/
public boolean allowMultipleEntriesPerFile()
{
return allowMultipleEntriesPerFile;
}
/**
* Specifies whether a schema file is allowed to contain multiple entries.
*
* @param allowMultipleEntriesPerFile
* Indicates whether a schema file is allowed to contain multiple
* entries. If this is {@code true}, then all entries in each
* file will be examined. If this is {@code false}, then only
* the first entry in each file will be examined and an error
* message will be reported for each file that contains multiple
* entries. In either case, an error will be reported for each
* schema file that does not contain any entries or that contains
* a malformed entry.
*/
public void setAllowMultipleEntriesPerFile(
final boolean allowMultipleEntriesPerFile)
{
this.allowMultipleEntriesPerFile = allowMultipleEntriesPerFile;
}
/**
* Indicates whether to examine files in subdirectories when provided with a
* schema path that is a directory. By default, subdirectories will not be
* examined and an error will be reported for each subdirectory that is
* encountered.
*
* @return {@code true} to examine files in subdirectories when provided with
* a schema path that is a directory, or {@code false} if only
* files directly contained in the provided directory will be
* examined and an error will be reported if the schema directory
* contains subdirectories.
*/
public boolean allowSchemaFilesInSubDirectories()
{
return allowSchemaFilesInSubDirectories;
}
/**
* Specifies whether to examine files in subdirectories when provided with a
* schema path that is a directory. This setting will be ignored if the
* {@link #validateSchema} method is called with a schema path that is a file
* rather than a directory.
*
* @param allowSchemaFilesInSubDirectories
* Indicates whether to examine files in subdirectories when
* provided with a schema path that is a directory. If this is
* {@code true}, then files in subdirectories will be examined,
* to any depth, with files in the directory processed first
* (in lexicographic order by name) and then subdirectories will
* be processed (also in lexicographic order by name). if this
* is {@code false}, then only files contained directly in the
* specified schema directory will be examined and an error
* will be reported for each subdirectory that is encountered.
*/
public void setAllowSchemaFilesInSubDirectories(
final boolean allowSchemaFilesInSubDirectories)
{
this.allowSchemaFilesInSubDirectories = allowSchemaFilesInSubDirectories;
}
/**
* Indicates whether to validate each entry containing the schema definitions
* using the schema that has been parsed thus far. By default, each entry
* will be validated to ensure that its contents conform to the schema that
* has been parsed from that file and all previous files.
*
* @return {@code true} if entries containing schema definitions should be
* validated according to the schema, or {@code false} if schema
* entries will not themselves be validated against the schema.
*/
public boolean ensureSchemaEntryIsValid()
{
return ensureSchemaEntryIsValid;
}
/**
* Specifies whether to validate each entry containing the schema definitions
* using the schema that has been parsed thus far.
*
* @param ensureSchemaEntryIsValid
* Indicates whether to validate each entry containing the schema
* definitions using the schema that has been parsed thus far.
* If this is {@code true}, then each entry will be validated
* to ensure that it conforms to the schema definitions read from
* that file and any previuos files that have already been
* processed and any errors identified will be reported If this
* is {@code false}, then schema entries will not be validated.
*/
public void setEnsureSchemaEntryIsValid(
final boolean ensureSchemaEntryIsValid)
{
this.ensureSchemaEntryIsValid = ensureSchemaEntryIsValid;
}
/**
* Retrieves an unmodifiable set of the schema element types that may be
* defined in schema files. By default, all types of schema elements may be
* defined.
*
* @return An unmodifiable set set of the schema element types that may be
* defined in schema files.
*/
@NotNull()
public Set getAllowedSchemaElementTypes()
{
return Collections.unmodifiableSet(allowedSchemaElementTypes);
}
/**
* Specifies the set of schema element types that may be defined in schema
* files.
*
* @param allowedSchemaElementTypes
* The set of schema element types that may be defined in schema
* files. It must not be {@code null} or empty.
*/
public void setAllowedSchemaElementTypes(
@NotNull final SchemaElementType... allowedSchemaElementTypes)
{
setAllowedSchemaElementTypes(StaticUtils.toList(allowedSchemaElementTypes));
}
/**
* Specifies the set of schema element types that may be defined in schema
* files.
*
* @param allowedSchemaElementTypes
* The set of schema element types that may be defined in schema
* files. It must not be {@code null} or empty.
*/
public void setAllowedSchemaElementTypes(
@NotNull final Collection allowedSchemaElementTypes)
{
Validator.ensureTrue(
((allowedSchemaElementTypes != null) &&
(! allowedSchemaElementTypes.isEmpty())),
"SchemaValidator.allowedSchemaElementTypes must not be null or " +
"empty.");
this.allowedSchemaElementTypes.clear();
this.allowedSchemaElementTypes.addAll(allowedSchemaElementTypes);
}
/**
* Retrieves the types of schema elements that can be referenced by other
* elements without the referenced types being known to the schema validator
* (e.g., by having been previously defined in the schema files). By default,
* no types of undefined elements may be referenced.
*
* @return The types of schema elements that can be referenced by other
* elements without the referenced types being known to the schema
* validator,
*/
@NotNull()
public Set getAllowReferencesToUndefinedElementTypes()
{
return Collections.unmodifiableSet(allowReferencesToUndefinedElementTypes);
}
/**
* Specifies the types of schema elements that can be referenced by other
* elements without the referenced types being known to the schema validator
* (e.g., by having been previously defined in the schema files).
*
* @param undefinedElementTypes
* The types of schema elements that can be referenced by other
* elements without the referenced types being known to the
* schema validator. It may be {@code null} or empty if no
* undefined schema elements will be permitted.
*/
public void setAllowReferencesToUndefinedElementTypes(
@Nullable final SchemaElementType... undefinedElementTypes)
{
setAllowReferencesToUndefinedElementTypes(
StaticUtils.toList(undefinedElementTypes));
}
/**
* Specifies the types of schema elements that can be referenced by other
* elements without the referenced types being known to the schema validator
* (e.g., by having been previously defined in the schema files).
*
* @param undefinedElementTypes
* The types of schema elements that can be referenced by other
* elements without the referenced types being known to the
* schema validator. It may be {@code null} or empty if no
* undefined schema elements will be permitted.
*/
public void setAllowReferencesToUndefinedElementTypes(
@Nullable final Collection undefinedElementTypes)
{
allowReferencesToUndefinedElementTypes.clear();
if (undefinedElementTypes != null)
{
allowReferencesToUndefinedElementTypes.addAll(undefinedElementTypes);
}
}
/**
* Indicates whether the same schema element may be defined multiple times.
* By default, each schema element may be defined only once.
*
* @return {@code true} if the same schema element may be defined multiple
* times, in which case subsequent definitions will override previous
* definitions, or {@code false} if an error will be reported if the
* same element is encountered multiple times.
*/
public boolean allowRedefiningElements()
{
return allowRedefiningElements;
}
/**
* Specifies whether the same schema element may be defined multiple times.
*
* @param allowRedefiningElements
* Indicates whether the same schema element may be defined
* multiple times. If this is {@code true}, then a schema
* element may be defined multiple times, with the most recent
* definition ultimately being used, as long as the redefined
* definition keeps the same OID and names of the former
* definition (although the redefined element may add additional
* names), but other details may be changed. If this is
* {@code false}, then any attempt to define the same element
* multiple times will be reported as an error.
*/
public void setAllowRedefiningElements(final boolean allowRedefiningElements)
{
this.allowRedefiningElements = allowRedefiningElements;
}
/**
* Indicates whether to allow schema elements that do not contain names but
* may only be identified by an OID (or by the rule ID in the case of DIT
* structure rules). Note that this does not apply to attribute syntaxes,
* which cannot have names and may only be referenced by OID. LDAP does allow
* schema elements without names, but such elements are not as user-friendly
* and it may not be desirable to have such definitions. By default, the
* schema validator will allow elements without names.
*
* @return {@code true} if the schema validator will elements without names,
* or {@code false} if an error will be reported for each schema
* element (other than attribute syntaxes) without a name.
*/
public boolean allowElementsWithoutNames()
{
return allowElementsWithoutNames;
}
/**
* Specifies whether to allow schema elements that do not contain names but
* may only be identified by an OID (or by the rule ID in the case of DIT
* structure rules). Note that this does not apply to attribute syntaxes,
* which cannot have names and may only be referenced by OID. LDAP does allow
* schema elements without names, but such elements are not as user-friendly
* and it may not be desirable to have such definitions.
*
* @param allowElementsWithoutNames
* Indicates whether to allow schema elements that do not contain
* names. If this is {@code true}, then elements without names
* will be allowed. If this is {@code false}, then an error will
* be reported for each element (other than attribute syntaxes)
* that does not have a name.
*/
public void setAllowElementsWithoutNames(
final boolean allowElementsWithoutNames)
{
this.allowElementsWithoutNames = allowElementsWithoutNames;
}
/**
* Indicates whether schema elements will be permitted to include non-numeric
* object identifiers that are comprised of the name for that element with
* "-oid" appended to it. For example, if an attribute is named "my-attr",
* then "my-attr-oid" may be allowed as an alternative to a numeric OID.
* While the official specification requires valid numeric OIDs, some servers
* are more relaxed about this requirement and allow OIDs to use the alternate
* form referenced above. By default, valid numeric OIDs will be required.
*
* @return {@code true} if non-numeric OIDs will be allowed if they are
* comprised of the schema element name followed by "-oid", or
* {@code false} if not.
*/
public boolean allowNonNumericOIDsUsingName()
{
return allowNonNumericOIDsUsingName;
}
/**
* Indicates whether schema elements will be permitted to include non-numeric
* object identifiers that are of a form other than one of the element names
* followed by "-oid". By default, valid numeric OIDs will be required.
*
* @return {@code true} if non-numeric OIDs will be allowed if they are not
* comprised of the schema element name followed by "-oid", or
* {@code false} if not.
*/
public boolean allowNonNumericOIDsNotUsingName()
{
return allowNonNumericOIDsNotUsingName;
}
/**
* Indicates whether to use strict validation for numeric object identifiers.
* If strict validation is to be used, then each OID must contain at least two
* components, the first component must be zero, one or, two, and the second
* component may only be greater than 39 if the first component is two. By
* default, strict validation will be performed for numeric OIDs.
*
* @return {@code true} if strict validation will be performed for numeric
* OIDs, or {@code false} if more relaxed validation will be used
* that only requires them to be comprised of a non-empty string
* comprised only of digits and periods that does not start or end
* with a period and does not contain consecutive periods.
*/
public boolean useStrictOIDValidation()
{
return useStrictOIDValidation;
}
/**
* Specifies the behavior to use when validating object identifiers.
*
* @param allowNonNumericOIDsUsingName
* Indicates whether to allow non-numeric OIDs if they are
* comprised of the name for the schema element followed by
* "-oid". If this is {@code true}, then non-numeric OIDs will
* be allowed if they use that form. If this is {@code false},
* then an error will be reported for schema elements with a
* non-numeric OID in that form.
* @param allowNonNumericOIDsNotUsingName
* Indicates whether to allow non-numeric OIDs if they are not
* comprised of the name for the schema element followed by
* "-oid". If this is {@code true}, then non-numeric OIDs will
* be allowed if they use that form. If this is {@code false},
* then an error will be reported for schema elements with a
* non-numeric OID that does not use the element name.
* @param useStrictOIDValidation
* Indicates whether to use strict validation for numeric
* object identifiers. If this is {@code false}, then numeric
* OIDs will be required to be comprised entirely of digits and
* periods, must not start or end with a period, and must not
* contain consecutive periods. If this is {@code true}, then
* numeric OIDs must also consist of at least two components,
* the first component must be zero, one or two, and the second
* component may only be greater than 39 if the first component
* is two.
*/
public void setOIDValidation(final boolean allowNonNumericOIDsUsingName,
final boolean allowNonNumericOIDsNotUsingName,
final boolean useStrictOIDValidation)
{
this.allowNonNumericOIDsUsingName = allowNonNumericOIDsUsingName;
this.allowNonNumericOIDsNotUsingName = allowNonNumericOIDsNotUsingName;
this.useStrictOIDValidation = useStrictOIDValidation;
}
/**
* Indicates whether to allow schema element names that start with a digit.
* LDAP specifications require that the first character of a schema element
* name be a letter, but some servers allow names that start with digits. By
* default, schema element names will not be allowed to start with a digit.
*
* @return {@code true} if schema element names will be permitted to start
* with digits, or {@code false} if an error will be reported for
* names that start with a digit.
*/
public boolean allowNamesWithInitialDigit()
{
return allowNamesWithInitialDigit;
}
/**
* Specifies whether to allow schema element names that start with a digit.
* LDAP specifications require that the first character of a schema element
* name be a letter, but some servers allow names that start with digits.
*
* @param allowNamesWithInitialDigit
* Indicates whether to allow schema element names that start
* with a digit. If this is {@code true}, then names will be
* permitted to start with a digit. If this is {@code false},
* then an error will be reported for each name that starts with
* a digit.
*/
public void setAllowNamesWithInitialDigit(
final boolean allowNamesWithInitialDigit)
{
this.allowNamesWithInitialDigit = allowNamesWithInitialDigit;
}
/**
* Indicates whether to allow schema element names that start with a hyphen.
* LDAP specifications require that the first character of a schema element
* name be a letter, but some servers allow names that start with hyphens. By
* default, schema element names will not be allowed to start with a hyphen.
*
* @return {@code true} if schema element names will be permitted to start
* with hyphens, or {@code false} if an error will be reported for
* names that start with a hyphen.
*/
public boolean allowNamesWithInitialHyphen()
{
return allowNamesWithInitialHyphen;
}
/**
* Specifies whether to allow schema element names that start with a hyphen.
* LDAP specifications require that the first character of a schema element
* name be a letter, but some servers allow names that start with hyphens.
*
* @param allowNamesWithInitialHyphen
* Indicates whether to allow schema element names that start
* with a hyphen. If this is {@code true}, then names will be
* permitted to start with a hyphen. If this is {@code false},
* then an error will be reported for each name that starts with
* a hyphen.
*/
public void setAllowNamesWithInitialHyphen(
final boolean allowNamesWithInitialHyphen)
{
this.allowNamesWithInitialHyphen = allowNamesWithInitialHyphen;
}
/**
* Indicates whether to allow schema element names that contain the underscore
* character. LDAP specifications do not permit underscores in schema element
* names, but some servers do allow it. By default, schema element names will
* not be allowed to contain an underscore.
*
* @return {@code true} if schema element names will be permitted to contain
* underscores, or {@code false} if an error will be reported for
* names that contain an underscore.
*/
public boolean allowNamesWithUnderscore()
{
return allowNamesWithUnderscore;
}
/**
* Indicates whether to allow schema element names that contain the underscore
* character. LDAP specifications do not permit underscores in schema element
* names, but some servers do allow it.
*
* @param allowNamesWithUnderscore
* Indicates whether to allow schema element names that contain
* the underscore character. If this is {@code true}, then names
* will be permitted to contain underscores. If this is
* {@code false}, then an error will be reported for each name
* that contains an underscore.
*/
public void setAllowNamesWithUnderscore(
final boolean allowNamesWithUnderscore)
{
this.allowNamesWithUnderscore = allowNamesWithUnderscore;
}
/**
* Indicates whether to allow schema elements to have empty descriptions.
* LDAP specifications forbid empty quoted strings in schema definitions, but
* some servers allow it. By default, empty descriptions will not be allowed,
* and an error will be reported for every schema element that has an empty
* description.
*
* @return {@code true} if empty descriptions will be allowed, or
* {@code false} if errors will be reported for schema elements with
* empty descriptions.
*/
public boolean allowEmptyDescription()
{
return allowEmptyDescription;
}
/**
* Specifies whether to allow schema elements to have empty descriptions.
* LDAP specifications forbid empty quoted strings in schema definitions, but
* some servers allow it.
*
* @param allowEmptyDescription
* Indicates whether to allow schema elements to have empty
* descriptions. If this is {@code true}, then schema elements
* will be permitted to have empty descriptions. If it is
* {@code false}, then an error will be reported for each
* schema element with an empty description.
*/
public void setAllowEmptyDescription(final boolean allowEmptyDescription)
{
this.allowEmptyDescription = allowEmptyDescription;
}
/**
* Retrieves a list of the attribute syntaxes that will be used in the course
* of validating attribute type definitions. By default, the schema validator
* will be preconfigured with a default set of standard attribute syntaxes
* (as set by the {@link #configureLDAPSDKDefaultAttributeSyntaxes} method),
* in addition to any attribute type definitions contained in schema entries.
*
* @return A list of the attribute syntaxes that will be used in the course
* of validating attribute type definitions, or an empty list if the
* list of available syntaxes will be defined in the schema files.
*/
@NotNull()
public List getAttributeSyntaxes()
{
return Collections.unmodifiableList(new ArrayList<>(attributeSyntaxList));
}
/**
* Specifies a set of attribute syntaxes that will be used in the course
* of validating attribute type definitions.
*
* @param attributeSyntaxes
* The set of attribute syntaxes that will be used in the course
* of validating attribute type definitions. It may be
* {@code null} or empty if only syntaxes defined in the schema
* files will be used.
*/
public void setAttributeSyntaxes(
@Nullable final Collection attributeSyntaxes)
{
attributeSyntaxList = new ArrayList<>();
attributeSyntaxMap = new HashMap<>();
if (attributeSyntaxes != null)
{
for (final AttributeSyntaxDefinition d : attributeSyntaxes)
{
attributeSyntaxList.add(d);
attributeSyntaxMap.put(StaticUtils.toLowerCase(d.getOID()), d);
}
}
}
/**
* Configures the schema validator to use a default set of attribute syntaxes
* that are known to the LDAP SDK. Any other syntaxes that may have been
* defined will be cleared.
*/
public void configureLDAPSDKDefaultAttributeSyntaxes()
{
try
{
final Set defaultSyntaxes =
new LinkedHashSet<>();
final Schema schema = Schema.getDefaultStandardSchema();
defaultSyntaxes.addAll(schema.getAttributeSyntaxes());
if (PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE)
{
defaultSyntaxes.add(new AttributeSyntaxDefinition(
"( 1.3.6.1.4.1.30221.1.3.1 DESC 'User Password Syntax' )"));
defaultSyntaxes.add(new AttributeSyntaxDefinition(
"( 1.3.6.1.4.1.30221.1.3.2 " +
"DESC 'Relative Subtree Specification' )"));
defaultSyntaxes.add(new AttributeSyntaxDefinition(
"( 1.3.6.1.4.1.30221.1.3.3 " +
"DESC 'Absolute Subtree Specification' )"));
defaultSyntaxes.add(new AttributeSyntaxDefinition(
"( 1.3.6.1.4.1.30221.1.3.4 " +
"DESC 'Sun-defined Access Control Information' )"));
defaultSyntaxes.add(new AttributeSyntaxDefinition(
"( 1.3.6.1.4.1.30221.2.3.1 DESC 'Compact Timestamp' )"));
defaultSyntaxes.add(new AttributeSyntaxDefinition(
"( 1.3.6.1.4.1.30221.2.3.2 DESC 'LDAP URL' )"));
defaultSyntaxes.add(new AttributeSyntaxDefinition(
"( 1.3.6.1.4.1.30221.2.3.3 DESC 'Hex String' )"));
defaultSyntaxes.add(new AttributeSyntaxDefinition(
"( 1.3.6.1.4.1.30221.2.3.4 DESC 'JSON Object' )"));
}
setAttributeSyntaxes(defaultSyntaxes);
}
catch (final Exception e)
{
// This should never happen.
Debug.debugException(e);
}
}
/**
* Indicates whether to allow attribute type definitions to be missing an
* attribute syntax definition, by neither directly specifying the attribute
* syntax OID nor referencing a superior attribute type from which the syntax
* will be inherited. The LDAP specification requires that each attribute
* type specify its syntax or inherit it from a superior type, but some
* directory servers will assume a default syntax (e.g., Directory String) for
* attribute types that do not specify it and are not configured with a
* superior type. By default, any attribute type that does not specify a
* syntax and cannot inherit it from a superior type will be flagged as an
* error.
*
* @return {@code true} if attribute type definitions will be permitted to
* omit both an attribute syntax and a superior type, or
* {@code false} if an error will be reported for each such attribute
* type.
*/
public boolean allowAttributeTypesWithoutSyntax()
{
return allowAttributeTypesWithoutSyntax;
}
/**
* Specifies whether to allow attribute type definitions to be missing an
* attribute syntax definition, by neither directly specifying the attribute
* syntax OID nor referencing a superior attribute type from which the syntax
* will be inherited. The LDAP specification requires that each attribute
* type specify its syntax or inherit it from a superior type, but some
* directory servers will assume a default syntax (e.g., Directory String) for
* attribute types that do not specify it and are not configured with a
* superior type.
*
* @param allowAttributeTypesWithoutSyntax
* Indicates whether to allow attribute type definitions to be
* missing an attribute syntax definition, by neither directly
* specifying the attribute syntax OID nor referencing a superior
* attribute type from which the syntax will be inherited. If
* this is {@code true}, then attribute types that do not specify
* either a syntax OID or a superior type will be permitted. If
* this is {@code false}, then an error will be reported for
* each such attribute type.
*/
public void setAllowAttributeTypesWithoutSyntax(
final boolean allowAttributeTypesWithoutSyntax)
{
this.allowAttributeTypesWithoutSyntax = allowAttributeTypesWithoutSyntax;
}
/**
* Retrieves a list of the matching rules that will be used in the course of
* of validating attribute type definitions. By default, the schema validator
* will be preconfigured with a default set of standard matching rules (as set
* by the {@link #configureLDAPSDKDefaultMatchingRules()} method), in addition
* to any matching rule definitions contained in schema entries.
*
* @return A list of the matching rules that will be used in the course of
* validating attribute type definitions, or an empty list if the
* list of matching rules will be defined in the schema files.
*/
@NotNull()
public List getMatchingRuleDefinitions()
{
return Collections.unmodifiableList(new ArrayList<>(matchingRuleList));
}
/**
* Specifies a set of matching rules that will be used in the course of
* validating attribute type definitions.
*
* @param matchingRules
* The set of attribute syntaxes that will be used in the course
* of validating attribute type definitions. It may be
* {@code null} or empty if only syntaxes defined in the schema
* files will be used.
*/
public void setMatchingRules(
@Nullable final Collection matchingRules)
{
matchingRuleList = new ArrayList<>();
matchingRuleMap = new HashMap<>();
if (matchingRules != null)
{
for (final MatchingRuleDefinition d : matchingRules)
{
matchingRuleList.add(d);
matchingRuleMap.put(StaticUtils.toLowerCase(d.getOID()), d);
for (final String name : d.getNames())
{
matchingRuleMap.put(StaticUtils.toLowerCase(name), d);
}
}
}
}
/**
* Configures the schema validator to use a default set of matching rules that
* that are known to the LDAP SDK. Any other syntaxes that may have been
* defined will be cleared.
*/
public void configureLDAPSDKDefaultMatchingRules()
{
try
{
final Set defaultMatchingRules =
new LinkedHashSet<>();
final Schema schema = Schema.getDefaultStandardSchema();
defaultMatchingRules.addAll(schema.getMatchingRules());
if (PING_IDENTITY_DIRECTORY_SERVER_AVAILABLE)
{
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.1.4.1 NAME 'ds-mr-double-metaphone-approx' " +
"DESC 'Double Metaphone Approximate Match' " +
"SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.1.4.2 NAME 'ds-mr-user-password-exact' " +
"DESC 'user password exact matching rule' " +
"SYNTAX 1.3.6.1.4.1.30221.1.3.1 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.1.4.3 NAME 'ds-mr-user-password-equality' " +
"DESC 'user password matching rule' " +
"SYNTAX 1.3.6.1.4.1.30221.1.3.1 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.1.4.4 NAME 'historicalCsnOrderingMatch' " +
"SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.1.4.902 NAME 'caseExactIA5SubstringsMatch' " +
"SYNTAX 1.3.6.1.4.1.1466.115.121.1.58 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.1 NAME 'compactTimestampMatch' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.1 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.2 NAME 'compactTimestampOrderingMatch' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.1 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.3 NAME 'ldapURLMatch' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.2 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.4 NAME 'hexStringMatch' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.3 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.5 NAME 'hexStringOrderingMatch' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.3 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.12 NAME 'jsonObjectExactMatch' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.13 " +
"NAME 'jsonObjectFilterExtensibleMatch' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.14 NAME 'relativeTimeExtensibleMatch' " +
"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.15 " +
"NAME 'jsonObjectCaseSensitiveNamesCaseSensitiveValues' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.16 " +
"NAME 'jsonObjectCaseInsensitiveNamesCaseSensitiveValues' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
defaultMatchingRules.add(new MatchingRuleDefinition(
"( 1.3.6.1.4.1.30221.2.4.17 NAME " +
"'jsonObjectCaseInsensitiveNamesCaseInsensitiveValues' " +
"SYNTAX 1.3.6.1.4.1.30221.2.3.4 )"));
}
setMatchingRules(defaultMatchingRules);
}
catch (final Exception e)
{
// This should never happen.
Debug.debugException(e);
}
}
/**
* Indicates whether to allow attribute type definitions to be missing an
* equality matching definition, by neither directly specifying the matching
* rule name or OID nor referencing a superior attribute type from which the
* matching rule will be inherited. It is technically legal to have an
* attribute type definition that does not include an equality matching rule
* and does not inherit an equality matching rule from a superior type, this
* may not be desirable, as the server should fall back to byte-for-byte
* matching (although some servers may assume a default matching rule based on
* the syntax). By default, attribute types that do not specify an equality
* matching rule will be permitted.
*
* @return {@code true} if attribute type definitions will be permitted to
* omit both an attribute syntax and a superior type, or
* {@code false} if an error will be reported for each such attribute
* type.
*/
public boolean allowAttributeTypesWithoutEqualityMatchingRule()
{
return allowAttributeTypesWithoutEqualityMatchingRule;
}
/**
* Indicates whether to allow attribute type definitions to be missing an
* equality matching definition, by neither directly specifying the matching
* rule name or OID nor referencing a superior attribute type from which the
* matching rule will be inherited. It is technically legal to have an
* attribute type definition that does not include an equality matching rule
* and does not inherit an equality matching rule from a superior type, this
* may not be desirable, as the server should fall back to byte-for-byte
* matching (although some servers may assume a default matching rule based on
* the syntax).
*
* @param allowAttributeTypesWithoutEqualityMatchingRule
* Specifies whether to allow attribute type definitions to be
* missing an equality matching definition, by neither directly
* specifying the matching rule name or OID nor referencing a
* superior attribute type from which the matching rule will be
* inherited. If this is {@code true}, then attribute types that
* do not specify either an equality matching rule or a superior
* type will be permitted. If this is {@code false}, then an
* error will be reported for each such attribute type.
*/
public void setAllowAttributeTypesWithoutEqualityMatchingRule(
final boolean allowAttributeTypesWithoutEqualityMatchingRule)
{
this.allowAttributeTypesWithoutEqualityMatchingRule =
allowAttributeTypesWithoutEqualityMatchingRule;
}
/**
* Indicates whether to allow object classes with multiple superior classes.
* This is allowed by LDAP specifications, but some servers do not support it.
* By default, object classes with multiple superior classes will be
* permitted.
*
* @return {@code true} if object classes will be allowed to have multiple
* superior classes, or {@code false} if an error will be reported
* for each object class with multiple superiors.
*/
public boolean allowMultipleSuperiorObjectClasses()
{
return allowMultipleSuperiorObjectClasses;
}
/**
* Specifies whether to allow object classes with multiple superior classes.
* This is allowed by LDAP specifications, but some servers do not support it.
*
* @param allowMultipleSuperiorObjectClasses
* Indicates whether to allow object classes with multiple
* superior classes. If this is {@code true}, then object
* classes with multiple superiors will be allowed. If this is
* {@code false}, then an error will be reported for each
* object class with more than one superior class.
*/
public void setAllowMultipleSuperiorObjectClasses(
final boolean allowMultipleSuperiorObjectClasses)
{
this.allowMultipleSuperiorObjectClasses =
allowMultipleSuperiorObjectClasses;
}
/**
* Indicates whether to allow structural object classes that do not declare a
* superior class. Technically, structural object classes must inherit
* from structural or abstract classes, although some servers may assume a
* default superior class of "top" for a structural class that does not
* declare any superiors. By default, an error will be reported for each
* structural object class that does not explicitly declare any superior
* class.
*
* @return {@code true} if object classes that do not declare their superiors
* will be permitted, ro {@code false} if an error will be reported
* for each structural class that does not declare any superiors.
*/
public boolean allowStructuralObjectClassWithoutSuperior()
{
return allowStructuralObjectClassWithoutSuperior;
}
/**
* Specifies whether to allow structural object classes that do not declare a
* superior class. Technically, structural object classes must inherit
* from structural or abstract classes, although some servers may assume a
* default superior class of "top" for a structural class that does not
* declare any superiors.
*
* @param allowStructuralObjectClassWithoutSuperior
* Indicates whether to allow structural object classes that do
* not declare a superior class. If this is {@code true}, then
* structural object classes that do not declare any superior
* class will be assumed to subclass "top". if this is
* {@code false}, then an error will be reported for each
* structural object class that does not define any superior
* class.
*/
public void setAllowStructuralObjectClassWithoutSuperior(
final boolean allowStructuralObjectClassWithoutSuperior)
{
this.allowStructuralObjectClassWithoutSuperior =
allowStructuralObjectClassWithoutSuperior;
}
/**
* Indicates whether to allow object classes with an invalid inheritance
* relationship. As per LDAP specifications, structural object classes can
* only inherit from structural or abstract classes, auxiliary classes can
* only inherit from auxiliary or abstract classes, and abstract classes can
* only inherit from other abstract classes. By default, the schema validator
* will report an error for any object class that violates this constraint.
*
* @return {@code true} if the schema validator will allow object classes
* with invalid inheritance relationships, or {@code false} if an
* error will be reported for each object class with an invalid
* superior class.
*/
public boolean allowInvalidObjectClassInheritance()
{
return allowInvalidObjectClassInheritance;
}
/**
* Specifies whether to allow object classes with an invalid inheritance
* relationship. As per LDAP specifications, structural object classes can
* only inherit from structural or abstract classes, auxiliary classes can
* only inherit from auxiliary or abstract classes, and abstract classes can
* only inherit from other abstract classes.
*
* @param allowInvalidObjectClassInheritance
* Indicates whether to allow object classes with an invalid
* inheritance relationship. If this is {@code true}, then
* invalid inheritance relationships will be allowed. If this is
* {@code false}, then an error will be reported for each
* object class with an invalid superior class reference.
*/
public void setAllowInvalidObjectClassInheritance(
final boolean allowInvalidObjectClassInheritance)
{
this.allowInvalidObjectClassInheritance =
allowInvalidObjectClassInheritance;
}
/**
* Indicates whether to allow collective attribute type definitions.
* Collective attributes (as described in RFC 3671) have read-only values that
* are generated by the server rather than provided by clients. Although they
* are part of the LDAP specification, some servers do not support them or
* provide alternate virtual attribute mechanisms. By default, collective
* attribute definitions will be allowed.
*
* @return {@code true} if collective attributes will be allowed, or
* {@code false} if the schema validator will report an error for
* each collective attribute type definition.
*/
public boolean allowCollectiveAttributes()
{
return allowCollectiveAttributes;
}
/**
* Specifies whether to allow collective attribute type definitions.
* Collective attributes (as described in RFC 3671) have read-only values that
* are generated by the server rather than provided by clients. Although they
* are part of the LDAP specification, some servers do not support them or
* provide alternate virtual attribute mechanisms.
*
* @param allowCollectiveAttributes
* Indicates whether to allow collective attribute type
* definitions. If this is {@code true}, then collective
* attribute type definitions will be allowed. If this is
* {@code false}, then an error will be reported for each
* collective attribute type definition.
*/
public void setAllowCollectiveAttributes(
final boolean allowCollectiveAttributes)
{
this.allowCollectiveAttributes = allowCollectiveAttributes;
}
/**
* Indicates whether to allow schema elements declared with the OBSOLETE
* modifier. Obsolete schema elements are those that are no longer active
* and cannot be used in updates, although some servers may not support
* obsolete schema elements. By default, obsolete elements will be allowed.
*
* @return {@code true} if schema elements declared with the OBSOLETE
* modifier will be allowed, or {@code false} if an error will be
* reported for each schema element declared as OBSOLETE.
*/
public boolean allowObsoleteElements()
{
return allowObsoleteElements;
}
/**
* Specifies whether to allow schema elements declared with the OBSOLETE
* modifier. Obsolete schema elements are those that are no longer active
* and cannot be used in updates, although some servers may not support
* obsolete schema elements.
*
* @param allowObsoleteElements
* Indicates whether to allow schema elements declared with the
* OBSOLETE modifier. If this is {@code true}, then obsolete
* elements will be allowed. If this is {@code false}, then
* an error will be reported for each OBSOLETE schema element.
*/
public void setAllowObsoleteElements(final boolean allowObsoleteElements)
{
this.allowObsoleteElements = allowObsoleteElements;
}
/**
* Validates the schema definitions in the file or set of files at the given
* path.
*
* @param schemaPath
* The file or directory containing the schema definitions to
* validate. It must not be {@code null}, and the target file
* or directory must exist. If it is a directory, then files in
* the directory will be processed in lexicographic order by
* filename, optionally restricted to files matching the schema
* file name pattern.
* @param existingSchema
* An existing schema to use in the course of validating
* definitions. It may be {@code null} if there is no existing
* schema and only the definitions read from the provided path
* should be used.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*
* @return A {@code Schema} object that contains the definitions that were
* loaded. This may include schema elements that were flagged as
* invalid (if they could be parsed). If an existing schema was
* already available, the schema that is returned will be a merged
* representation of the existing schema and the newly loaded schema.
* This may be {@code null} if an error prevented any schema files
* from being processed and no existing schema was provided.
*/
@Nullable()
public Schema validateSchema(@NotNull final File schemaPath,
@Nullable final Schema existingSchema,
@NotNull final List errorMessages)
{
final boolean originalAllowEmptyDescription =
SchemaElement.allowEmptyDescription();
try
{
SchemaElement.setAllowEmptyDescription(true);
final int originalErrorMessagesSize = errorMessages.size();
final AtomicInteger schemaFilesProcessed = new AtomicInteger(0);
final List nonSchemaFilesIgnored = new ArrayList<>();
final Schema schema = validateSchema(schemaPath, errorMessages,
existingSchema, schemaFilesProcessed, nonSchemaFilesIgnored);
// If no error messages were written, and if no schema files were
// processed, then add an error message to indicate that.
if ((schemaFilesProcessed.get() == 0) &&
(errorMessages.size() == originalErrorMessagesSize))
{
switch (nonSchemaFilesIgnored.size())
{
case 0:
errorMessages.add(
ERR_SCHEMA_VALIDATOR_NO_SCHEMA_FILES_NONE_IGNORED.get(
schemaPath.getAbsolutePath()));
break;
case 1:
errorMessages.add(
ERR_SCHEMA_VALIDATOR_NO_SCHEMA_FILES_ONE_IGNORED.get(
schemaPath.getAbsolutePath(),
nonSchemaFilesIgnored.get(0).getAbsolutePath()));
break;
default:
final StringBuilder buffer = new StringBuilder();
final Iterator fileIterator =
nonSchemaFilesIgnored.iterator();
while (fileIterator.hasNext())
{
buffer.append('\'');
buffer.append(fileIterator.next().getAbsolutePath());
buffer.append('\'');
if (fileIterator.hasNext())
{
buffer.append(", ");
}
}
errorMessages.add(
ERR_SCHEMA_VALIDATOR_NO_SCHEMA_FILES_MULTIPLE_IGNORED.get(
schemaPath.getAbsolutePath(), buffer.toString()));
break;
}
}
return schema;
}
finally
{
SchemaElement.setAllowEmptyDescription(originalAllowEmptyDescription);
}
}
/**
* Validates the schema definitions in the file or set of files at the given
* path.
*
* @param schemaPath
* The file or directory containing the schema definitions to
* validate. It must not be {@code null}, and the target file
* or directory must exist. If it is a directory, then files in
* the directory will be processed in lexicographic order by
* filename, optionally restricted to files matching the schema
* file name pattern.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
* @param existingSchema
* The existing schema to use in the course of validating
* definitions. It may be {@code null} if there is no
* existing schema and only the definitions read from the
* provided path should be used.
* @param schemaFilesProcessed
* A counter that will be incremented for each schema file that
* is processed.
* @param nonSchemaFilesIgnored
* A list that should be updated with any files that are ignored
* because they do not match the configured schema file name
* pattern.
*
* @return A {@code Schema} object that contains the definitions that were
* loaded. If an existing schema was already available, the schema
* that is returned will be a merged representation of the existing
* schema and the newly loaded schema. This may be {@code null} if
* an error prevented any schema files from being processed and no
* existing schema was provided.
*/
@Nullable()
private Schema validateSchema(@NotNull final File schemaPath,
@NotNull final List errorMessages,
@Nullable final Schema existingSchema,
@NotNull final AtomicInteger schemaFilesProcessed,
@NotNull final List nonSchemaFilesIgnored)
{
// Make sure the schema path represents a file or directory that exists.
if (! schemaPath.exists())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NO_SUCH_PATH.get(
schemaPath.getAbsolutePath()));
return existingSchema;
}
else if (schemaPath.isDirectory())
{
return validateSchemaDirectory(schemaPath, errorMessages, existingSchema,
schemaFilesProcessed, nonSchemaFilesIgnored);
}
else
{
return validateSchemaFile(schemaPath, errorMessages, existingSchema,
schemaFilesProcessed, nonSchemaFilesIgnored);
}
}
/**
* Identifies and validates all schema files in the provided directory.
*
* @param schemaDirectory
* The directory containing the schema files to examine. It must
* must not be {@code null}, it must exist, and it must be a
* directory.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
* @param existingSchema
* The existing schema to use in the course of validating
* definitions. It may be {@code null} if there is no
* existing schema and only the definitions read from the
* provided path should be used.
* @param schemaFilesProcessed
* A counter that will be incremented for each schema file that
* is processed.
* @param nonSchemaFilesIgnored
* A list that should be updated with any files that are ignored
* because they do not match the configured schema file name
* pattern.
*
* @return A {@code Schema} object that contains the definitions that were
* loaded. If an existing schema was already available, the schema
* that is returned will be a merged representation of the existing
* schema and the newly loaded schema. This may be {@code null} if
* an error prevented any schema files from being processed and no
* existing schema was provided.
*/
@Nullable()
private Schema validateSchemaDirectory(@NotNull final File schemaDirectory,
@NotNull final List errorMessages,
@Nullable final Schema existingSchema,
@NotNull final AtomicInteger schemaFilesProcessed,
@NotNull final List nonSchemaFilesIgnored)
{
final TreeMap schemaFiles = new TreeMap<>();
final TreeMap subDirectories = new TreeMap<>();
for (final File f : schemaDirectory.listFiles())
{
final String name = f.getName();
if (f.isFile())
{
schemaFiles.put(name, f);
}
else
{
if (allowSchemaFilesInSubDirectories)
{
subDirectories.put(name, f);
}
else
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DIR_CONTAINS_SUBDIR.get(
schemaDirectory.getAbsolutePath(), name));
}
}
}
Schema schema = existingSchema;
for (final File f : schemaFiles.values())
{
final Schema newSchema = validateSchemaFile(f, errorMessages,
schema, schemaFilesProcessed, nonSchemaFilesIgnored);
if (schema == null)
{
schema = newSchema;
}
else
{
schema = Schema.mergeSchemas(schema, newSchema);
}
}
for (final File f : subDirectories.values())
{
final Schema newSchema =
validateSchemaDirectory(f, errorMessages, schema,
schemaFilesProcessed, nonSchemaFilesIgnored);
if (schema == null)
{
schema = newSchema;
}
else
{
schema = Schema.mergeSchemas(schema, newSchema);
}
}
return schema;
}
/**
* Validates the schema definitions in the specified file.
*
* @param schemaFile
* The file containing the schema definitions to validate. It
* must not be {@code null}, it must exist, and it must be a
* file.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
* @param existingSchema
* The existing schema to use in the course of validating
* definitions. It may be {@code null} if there is no
* existing schema and only the definitions read from the
* provided path should be used.
* @param schemaFilesProcessed
* A counter that will be incremented for each schema file that
* is processed.
* @param nonSchemaFilesIgnored
* A list that should be updated with any files that are ignored
* because they do not match the configured schema file name
* pattern.
*
* @return A {@code Schema} object that contains the definitions that were
* loaded. If an existing schema was already available, the schema
* that is returned will be a merged representation of the existing
* schema and the newly loaded schema. This may be {@code null} if
* an error prevented any schema files from being processed and no
* existing schema was provided.
*/
@Nullable()
private Schema validateSchemaFile(@NotNull final File schemaFile,
@NotNull final List errorMessages,
@Nullable final Schema existingSchema,
@NotNull final AtomicInteger schemaFilesProcessed,
@NotNull final List nonSchemaFilesIgnored)
{
if (schemaFileNamePattern != null)
{
final String name = schemaFile.getName();
if (! schemaFileNamePattern.matcher(name).matches())
{
if (ignoreSchemaFilesNotMatchingFileNamePattern)
{
nonSchemaFilesIgnored.add(schemaFile);
}
else
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_FILE_NAME_DOES_NOT_MATCH_PATTERN.get(
schemaFile.getAbsoluteFile().getParentFile().
getAbsolutePath(),
name));
}
return existingSchema;
}
}
schemaFilesProcessed.incrementAndGet();
Schema newSchema = existingSchema;
try (LDIFReader ldifReader = new LDIFReader(schemaFile))
{
Entry schemaEntry = ldifReader.readEntry();
if (schemaEntry == null)
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NO_ENTRY_IN_FILE.get(
schemaFile.getAbsolutePath()));
return existingSchema;
}
newSchema = validateSchemaEntry(schemaEntry, schemaFile, errorMessages,
newSchema);
while (true)
{
schemaEntry = ldifReader.readEntry();
if (schemaEntry == null)
{
break;
}
if (! allowMultipleEntriesPerFile)
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MULTIPLE_ENTRIES_IN_FILE.get(
schemaFile.getAbsolutePath()));
return newSchema;
}
newSchema = validateSchemaEntry(schemaEntry, schemaFile, errorMessages,
newSchema);
}
}
catch (final IOException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_ERROR_READING_FILE.get(
schemaFile.getAbsolutePath(), StaticUtils.getExceptionMessage(e)));
}
catch (final LDIFException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_MALFORMED_LDIF_ENTRY.get(
schemaFile.getAbsolutePath(), e.getMessage()));
}
return newSchema;
}
/**
* Validates the schema definitions in the provided entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
* @param existingSchema
* The existing schema to use in the course of validating
* definitions. It may be {@code null} if there is no
* existing schema and only the definitions read from the
* provided path should be used.
*
* @return A {@code Schema} object that contains the definitions that were
* validated. If an existing schema was already available, the
* schema that is returned will be a merged representation of the
* existing schema and the newly loaded schema.
*/
@NotNull()
private Schema validateSchemaEntry(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@NotNull final List errorMessages,
@Nullable final Schema existingSchema)
{
if (schemaEntry.hasAttribute(Schema.ATTR_ATTRIBUTE_SYNTAX))
{
validateAttributeSyntaxes(schemaEntry, schemaFile, errorMessages);
}
if (schemaEntry.hasAttribute(Schema.ATTR_MATCHING_RULE))
{
if (attributeSyntaxMap.isEmpty())
{
configureLDAPSDKDefaultAttributeSyntaxes();
}
validateMatchingRules(schemaEntry, schemaFile, existingSchema,
errorMessages);
}
if (schemaEntry.hasAttribute(Schema.ATTR_ATTRIBUTE_TYPE))
{
if (attributeSyntaxMap.isEmpty())
{
configureLDAPSDKDefaultAttributeSyntaxes();
}
if (matchingRuleMap.isEmpty())
{
configureLDAPSDKDefaultMatchingRules();
}
final Map attributeTypeMap =
new HashMap<>();
validateAttributeTypes(schemaEntry, schemaFile, attributeTypeMap,
existingSchema, errorMessages);
}
if (schemaEntry.hasAttribute(Schema.ATTR_OBJECT_CLASS))
{
final Entry schemaEntryWithoutObjectClasses = schemaEntry.duplicate();
schemaEntryWithoutObjectClasses.removeAttribute(Schema.ATTR_OBJECT_CLASS);
Schema s = new Schema(schemaEntryWithoutObjectClasses);
if (existingSchema != null)
{
s = Schema.mergeSchemas(existingSchema, s);
}
final Map objectClassMap = new HashMap<>();
validateObjectClasses(schemaEntry, schemaFile, objectClassMap, s,
errorMessages);
}
if (schemaEntry.hasAttribute(Schema.ATTR_NAME_FORM))
{
final Entry schemaEntryWithoutNameForms = schemaEntry.duplicate();
schemaEntryWithoutNameForms.removeAttribute(Schema.ATTR_NAME_FORM);
Schema s = new Schema(schemaEntryWithoutNameForms);
if (existingSchema != null)
{
s = Schema.mergeSchemas(existingSchema, s);
}
final Map nameFormsByNameOrOID =
new HashMap<>();
final Map nameFormsByOC =
new HashMap<>();
validateNameForms(schemaEntry, schemaFile, nameFormsByNameOrOID,
nameFormsByOC, s, errorMessages);
}
if (schemaEntry.hasAttribute(Schema.ATTR_DIT_CONTENT_RULE))
{
final Entry schemaEntryWithoutDITContentRules = schemaEntry.duplicate();
schemaEntryWithoutDITContentRules.removeAttribute(
Schema.ATTR_DIT_CONTENT_RULE);
Schema s = new Schema(schemaEntryWithoutDITContentRules);
if (existingSchema != null)
{
s = Schema.mergeSchemas(existingSchema, s);
}
final Map dcrMap = new HashMap<>();
validateDITContentRules(schemaEntry, schemaFile, dcrMap, s,
errorMessages);
}
if (schemaEntry.hasAttribute(Schema.ATTR_DIT_STRUCTURE_RULE))
{
final Entry schemaEntryWithoutDITStructureRules = schemaEntry.duplicate();
schemaEntryWithoutDITStructureRules.removeAttribute(
Schema.ATTR_DIT_STRUCTURE_RULE);
Schema s = new Schema(schemaEntryWithoutDITStructureRules);
if (existingSchema != null)
{
s = Schema.mergeSchemas(existingSchema, s);
}
final Map dsrIDAndNameMap =
new HashMap<>();
final Map dsrNFMap =
new HashMap<>();
validateDITStructureRules(schemaEntry, schemaFile, dsrIDAndNameMap,
dsrNFMap, s, errorMessages);
}
if (schemaEntry.hasAttribute(Schema.ATTR_MATCHING_RULE_USE))
{
final Entry schemaEntryWithoutMatchingRuleUses = schemaEntry.duplicate();
schemaEntryWithoutMatchingRuleUses.removeAttribute(
Schema.ATTR_MATCHING_RULE_USE);
Schema s = new Schema(schemaEntryWithoutMatchingRuleUses);
if (existingSchema != null)
{
s = Schema.mergeSchemas(existingSchema, s);
}
final Map mruMap = new HashMap<>();
validateMatchingRuleUses(schemaEntry, schemaFile, mruMap, s,
errorMessages);
}
Schema s = new Schema(schemaEntry);
if (existingSchema != null)
{
s = Schema.mergeSchemas(existingSchema, s);
}
if (ensureSchemaEntryIsValid)
{
final List schemaEntryInvalidReasons = new ArrayList<>();
final EntryValidator entryValidator = new EntryValidator(s);
if (! entryValidator.entryIsValid(schemaEntry, schemaEntryInvalidReasons))
{
for (final String invalidReason : schemaEntryInvalidReasons)
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_ENTRY_NOT_VALID.get(
schemaEntry.getDN(), schemaFile.getAbsolutePath(),
invalidReason));
}
}
}
return s;
}
/**
* Validates any attribute syntax definitions contained in the provided
* schema entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateAttributeSyntaxes(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@NotNull final List errorMessages)
{
for (final String syntaxString :
schemaEntry.getAttributeValues(Schema.ATTR_ATTRIBUTE_SYNTAX))
{
// If attribute syntaxes aren't allowed, then report an error without
// doing anything else.
if (! allowedSchemaElementTypes.contains(
SchemaElementType.ATTRIBUTE_SYNTAX))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_SYNTAX_NOT_ALLOWED.get(
schemaFile.getAbsolutePath(), syntaxString));
continue;
}
// Make sure that we can parse the syntax definition.
final AttributeSyntaxDefinition syntax;
try
{
syntax = new AttributeSyntaxDefinition(syntaxString);
}
catch (final LDAPException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_SYNTAX.get(
syntaxString, schemaFile.getAbsolutePath(), e.getMessage()));
continue;
}
// Make sure that the syntax has a valid numeric OID.
try
{
validateOID(syntax.getOID(), StaticUtils.NO_STRINGS);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_SYNTAX_INVALID_OID.get(
syntaxString, schemaFile.getAbsolutePath(), e.getMessage()));
}
// If the syntax has a description, then make sure it's not empty.
if (! allowEmptyDescription)
{
final String description = syntax.getDescription();
if ((description != null) && description.isEmpty())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_SYNTAX_EMPTY_DESCRIPTION.get(
syntaxString, schemaFile.getAbsolutePath()));
}
}
// Make sure that the syntax isn't already defined.
final String lowerOID = StaticUtils.toLowerCase(syntax.getOID());
final AttributeSyntaxDefinition existingSyntax =
attributeSyntaxMap.get(lowerOID);
if ((existingSyntax != null) && (! allowRedefiningElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_SYNTAX_ALREADY_DEFINED.get(
syntaxString, schemaFile.getAbsolutePath(),
existingSyntax.toString()));
}
attributeSyntaxMap.put(lowerOID, syntax);
}
}
/**
* Validates any matching rule definitions contained in the provided schema
* entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param existingSchema
* An existing schema that has already been read (e.g., from
* earlier schema files). It may be {@code null} if only the
* elements from the current file should be used.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateMatchingRules(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@Nullable final Schema existingSchema,
@NotNull final List errorMessages)
{
for (final String matchingRuleString :
schemaEntry.getAttributeValues(Schema.ATTR_MATCHING_RULE))
{
// If matching rules aren't allowed, then report an error without doing
// anything else.
if (! allowedSchemaElementTypes.contains(
SchemaElementType.MATCHING_RULE))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_NOT_ALLOWED.get(
schemaFile.getAbsolutePath(), matchingRuleString));
continue;
}
// Make sure that we can parse the matching rule definition.
final MatchingRuleDefinition matchingRule;
try
{
matchingRule = new MatchingRuleDefinition(matchingRuleString);
}
catch (final LDAPException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_MR.get(
matchingRuleString, schemaFile.getAbsolutePath(), e.getMessage()));
continue;
}
// Make sure that the matching rule has a valid numeric OID.
try
{
validateOID(matchingRule.getOID(), matchingRule.getNames());
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_INVALID_OID.get(
matchingRuleString, schemaFile.getAbsolutePath(), e.getMessage()));
}
// Make sure that all of the names are valid.
if ((matchingRule.getNames().length == 0) &&
(! allowElementsWithoutNames))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_NO_NAME.get(
matchingRuleString, schemaFile.getAbsolutePath()));
}
for (final String name : matchingRule.getNames())
{
try
{
validateName(name);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_INVALID_NAME.get(
matchingRuleString, schemaFile.getAbsolutePath(), name,
e.getMessage()));
}
}
// If the matching rule has a description, then make sure it's not empty.
if (! allowEmptyDescription)
{
final String description = matchingRule.getDescription();
if ((description != null) && description.isEmpty())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_EMPTY_DESCRIPTION.get(
matchingRuleString, schemaFile.getAbsolutePath()));
}
}
// If the matching rule is declared obsolete, then make sure that's
// allowed.
if (matchingRule.isObsolete() && (! allowObsoleteElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_OBSOLETE.get(
matchingRuleString, schemaFile.getAbsolutePath()));
}
// Make sure that the syntax OID is valid.
final String syntaxOID = matchingRule.getSyntaxOID();
try
{
validateOID(syntaxOID, StaticUtils.NO_STRINGS);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_INVALID_SYNTAX_OID.get(
matchingRuleString, schemaFile.getAbsolutePath(), syntaxOID,
e.getMessage()));
}
// Make sure that the syntax OID is one that we know about.
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_SYNTAX))
{
final String lowerSyntaxOID = StaticUtils.toLowerCase(syntaxOID);
if (! attributeSyntaxMap.containsKey(lowerSyntaxOID))
{
if ((existingSchema == null) ||
existingSchema.getAttributeSyntax(lowerSyntaxOID) == null)
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_UNDEFINED_SYNTAX.get(
matchingRuleString, schemaFile.getAbsolutePath(), syntaxOID));
}
}
}
// Make sure that the matching rule isn't already defined.
boolean isDuplicate = false;
final String lowerOID = StaticUtils.toLowerCase(matchingRule.getOID());
if (matchingRuleMap.containsKey(lowerOID) && (! allowRedefiningElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MR_ALREADY_DEFINED_WITH_OID.get(
matchingRuleString, schemaFile.getAbsolutePath(),
matchingRuleMap.get(lowerOID).toString()));
isDuplicate = true;
}
if (! isDuplicate)
{
for (final String name : matchingRule.getNames())
{
final String lowerName = StaticUtils.toLowerCase(name);
if (matchingRuleMap.containsKey(lowerName) &&
(! allowRedefiningElements))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_MR_ALREADY_DEFINED_WITH_NAME.get(
matchingRuleString, schemaFile.getAbsolutePath(),
name, matchingRuleMap.get(lowerName).toString()));
isDuplicate = true;
break;
}
}
}
if (! isDuplicate)
{
matchingRuleMap.put(lowerOID, matchingRule);
for (final String name : matchingRule.getNames())
{
matchingRuleMap.put(StaticUtils.toLowerCase(name), matchingRule);
}
}
}
}
/**
* Validates any attribute type definitions contained in the provided schema
* entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param attributeTypeMap
* A map of the attribute type definitions that have already been
* parsed from the same file. It must not be {@code null} (but
* may be empty), and it must be updatable.
* @param existingSchema
* An existing schema that has already been read (e.g., from
* earlier schema files). It may be {@code null} if only the
* elements from the current file should be used.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateAttributeTypes(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@NotNull final Map attributeTypeMap,
@Nullable final Schema existingSchema,
@NotNull final List errorMessages)
{
for (final String attributeTypeString :
schemaEntry.getAttributeValues(Schema.ATTR_ATTRIBUTE_TYPE))
{
// If attribute types aren't allowed, then report an error without doing
// anything else.
if (! allowedSchemaElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_NOT_ALLOWED.get(
schemaFile.getAbsolutePath(), attributeTypeString));
continue;
}
// Make sure that we can parse the attribute type definition.
final AttributeTypeDefinition attributeType;
try
{
attributeType = new AttributeTypeDefinition(attributeTypeString);
}
catch (final LDAPException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_AT.get(
attributeTypeString, schemaFile.getAbsolutePath(),
e.getMessage()));
continue;
}
// Make sure that the attribute type has a valid numeric OID.
try
{
validateOID(attributeType.getOID(), attributeType.getNames());
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_INVALID_OID.get(
attributeTypeString, schemaFile.getAbsolutePath(),
e.getMessage()));
}
// Make sure that all of the names are valid.
if ((attributeType.getNames().length == 0) &&
(! allowElementsWithoutNames))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_NO_NAME.get(
attributeTypeString, schemaFile.getAbsolutePath()));
}
for (final String name : attributeType.getNames())
{
try
{
validateName(name);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_INVALID_NAME.get(
attributeTypeString, schemaFile.getAbsolutePath(), name,
e.getMessage()));
}
}
// If the attribute type has a description, then make sure it's not empty.
if (! allowEmptyDescription)
{
final String description = attributeType.getDescription();
if ((description != null) && description.isEmpty())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_EMPTY_DESCRIPTION.get(
attributeTypeString, schemaFile.getAbsolutePath()));
}
}
// If the attribute type is declared obsolete, then make sure that's
// allowed.
if (attributeType.isObsolete() && (! allowObsoleteElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_OBSOLETE.get(
attributeTypeString, schemaFile.getAbsolutePath()));
}
// Check to see if there is a superior type, and if so, whether it's
// defined.
AttributeTypeDefinition superiorType;
final String superiorTypeNameOrOID = attributeType.getSuperiorType();
if (superiorTypeNameOrOID == null)
{
superiorType = null;
}
else
{
final String lowerSuperiorTypeNameOrOID =
StaticUtils.toLowerCase(superiorTypeNameOrOID);
superiorType = attributeTypeMap.get(lowerSuperiorTypeNameOrOID);
if ((superiorType == null) && (existingSchema != null))
{
superiorType =
existingSchema.getAttributeType(lowerSuperiorTypeNameOrOID);
}
if ((superiorType == null) &&
(! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE)))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_SUPERIOR.get(
attributeTypeString, schemaFile.getAbsolutePath(),
superiorTypeNameOrOID));
}
}
// Check to see if there is an equality matching rule. If not, then we
// may want to check to make sure that there is a superior type because
// an attribute type without an equality matching rule can be problematic.
final String equalityMRNameOrOID =
attributeType.getEqualityMatchingRule();
if (equalityMRNameOrOID == null)
{
if ((superiorTypeNameOrOID == null) &&
(! allowAttributeTypesWithoutEqualityMatchingRule))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_NO_EQ_MR.get(
attributeTypeString, schemaFile.getAbsolutePath()));
}
}
// Check to make sure that any declared matching rules are defined in the
// schema.
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.MATCHING_RULE))
{
if (equalityMRNameOrOID != null)
{
if (! matchingRuleMap.containsKey(
StaticUtils.toLowerCase(equalityMRNameOrOID)))
{
if ((existingSchema == null) ||
(existingSchema.getMatchingRule(equalityMRNameOrOID) == null))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_EQ_MR.get(
attributeTypeString, schemaFile.getAbsolutePath(),
equalityMRNameOrOID));
}
}
}
final String orderingMRNameOrOID =
attributeType.getOrderingMatchingRule();
if (orderingMRNameOrOID != null)
{
if (! matchingRuleMap.containsKey(
StaticUtils.toLowerCase(orderingMRNameOrOID)))
{
if ((existingSchema == null) ||
(existingSchema.getMatchingRule(orderingMRNameOrOID) == null))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_ORD_MR.get(
attributeTypeString, schemaFile.getAbsolutePath(),
orderingMRNameOrOID));
}
}
}
final String substringMRNameOrOID =
attributeType.getSubstringMatchingRule();
if (substringMRNameOrOID != null)
{
if (! matchingRuleMap.containsKey(
StaticUtils.toLowerCase(substringMRNameOrOID)))
{
if ((existingSchema == null) ||
(existingSchema.getMatchingRule(substringMRNameOrOID) == null))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_SUB_MR.get(
attributeTypeString, schemaFile.getAbsolutePath(),
substringMRNameOrOID));
}
}
}
}
// Check to see if there's a syntax. If not, make sure there's a
// superior type. Otherwise, make sure the syntax OID is valid and
// references a known syntax.
final String syntaxOID = attributeType.getSyntaxOID();
if (syntaxOID == null)
{
if ((superiorTypeNameOrOID == null) &&
(! allowAttributeTypesWithoutSyntax))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_NO_SYNTAX.get(
attributeTypeString, schemaFile.getAbsolutePath()));
}
}
else if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_SYNTAX))
{
final String baseOID =
AttributeTypeDefinition.getBaseSyntaxOID(syntaxOID);
try
{
validateOID(baseOID, StaticUtils.NO_STRINGS);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_INVALID_SYNTAX_OID.get(
attributeTypeString, schemaFile.getAbsolutePath(), baseOID,
e.getMessage()));
}
final String lowerSyntaxOID = StaticUtils.toLowerCase(baseOID);
if (! attributeSyntaxMap.containsKey(lowerSyntaxOID))
{
if ((existingSchema == null) ||
(existingSchema.getAttributeSyntax(lowerSyntaxOID) == null))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_UNDEFINED_SYNTAX.get(
attributeTypeString, schemaFile.getAbsolutePath(), baseOID));
}
}
}
// Check to see if the attribute type is collective, and if so whether
// that is allowed.
if (attributeType.isCollective() && (! allowCollectiveAttributes))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_AT_COLLECTIVE.get(
attributeTypeString, schemaFile.getAbsolutePath()));
}
// Check to see if the attribute type is declared as NO-USER-MODIFICATION,
// and if so, then make sure it has an operational usage.
if (attributeType.isNoUserModification() &&
(! attributeType.isOperational()))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_AT_NO_USER_MOD_WITHOUT_OP_USAGE.get(
attributeTypeString, schemaFile.getAbsolutePath()));
}
// Make sure that the attribute type isn't already defined.
boolean isDuplicate = false;
if (! allowRedefiningElements)
{
final String lowerOID = StaticUtils.toLowerCase(attributeType.getOID());
AttributeTypeDefinition existingDefinition =
attributeTypeMap.get(lowerOID);
if ((existingDefinition == null) && (existingSchema != null))
{
existingDefinition = existingSchema.getAttributeType(lowerOID);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_AT_ALREADY_DEFINED_WITH_OID.get(
attributeTypeString, schemaFile.getAbsolutePath(),
existingDefinition.toString()));
isDuplicate = true;
}
if (! isDuplicate)
{
for (final String name : attributeType.getNames())
{
final String lowerName = StaticUtils.toLowerCase(name);
existingDefinition = attributeTypeMap.get(lowerName);
if ((existingDefinition == null) && (existingSchema != null))
{
existingDefinition = existingSchema.getAttributeType(lowerName);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_AT_ALREADY_DEFINED_WITH_NAME.get(
attributeTypeString, schemaFile.getAbsolutePath(),
name, existingDefinition.toString()));
isDuplicate = true;
break;
}
}
}
}
// Add the attribute type to the map so it can be referenced by
// subordinate types.
if (! isDuplicate)
{
attributeTypeMap.put(StaticUtils.toLowerCase(attributeType.getOID()),
attributeType);
for (final String name : attributeType.getNames())
{
attributeTypeMap.put(StaticUtils.toLowerCase(name), attributeType);
}
}
}
}
/**
* Validates any object class definitions contained in the provided schema
* entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param objectClassMap
* A map of the object class definitions that have already been
* parsed from the same file. It must not be {@code null} (but
* may be empty), and it must be updatable.
* @param existingSchema
* An existing schema that has already been read (e.g., from
* earlier schema files). It must not be {@code null}.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateObjectClasses(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@NotNull final Map objectClassMap,
@Nullable final Schema existingSchema,
@NotNull final List errorMessages)
{
for (final String objectClassString :
schemaEntry.getAttributeValues(Schema.ATTR_OBJECT_CLASS))
{
// If object classes aren't allowed, then report an error without doing
// anything else.
if (! allowedSchemaElementTypes.contains(SchemaElementType.OBJECT_CLASS))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_NOT_ALLOWED.get(
schemaFile.getAbsolutePath(), objectClassString));
continue;
}
// Make sure that we can parse the object class definition.
final ObjectClassDefinition objectClass;
try
{
objectClass = new ObjectClassDefinition(objectClassString);
}
catch (final LDAPException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_OC.get(
objectClassString, schemaFile.getAbsolutePath(), e.getMessage()));
continue;
}
// Make sure that the object class has a valid numeric OID.
try
{
validateOID(objectClass.getOID(), objectClass.getNames());
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_INVALID_OID.get(
objectClassString, schemaFile.getAbsolutePath(), e.getMessage()));
}
// Make sure that all of the names are valid.
if ((objectClass.getNames().length == 0) &&
(! allowElementsWithoutNames))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_NO_NAME.get(
objectClassString, schemaFile.getAbsolutePath()));
}
for (final String name : objectClass.getNames())
{
try
{
validateName(name);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_INVALID_NAME.get(
objectClassString, schemaFile.getAbsolutePath(), name,
e.getMessage()));
}
}
// If the object class has a description, then make sure it's not empty.
if (! allowEmptyDescription)
{
final String description = objectClass.getDescription();
if ((description != null) && description.isEmpty())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_EMPTY_DESCRIPTION.get(
objectClassString, schemaFile.getAbsolutePath()));
}
}
// If the object class is declared obsolete, then make sure that's
// allowed.
if (objectClass.isObsolete() && (! allowObsoleteElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_OBSOLETE.get(
objectClassString, schemaFile.getAbsolutePath()));
}
// Validate all of the superior object classes.
validateSuperiorObjectClasses(schemaFile, objectClass, objectClassMap,
existingSchema, errorMessages);
// Validate all of the required and optional attribute types.
final Set requiredAttrNamesAndOIDs = new HashSet<>();
for (final String attrNameOrOID : objectClass.getRequiredAttributes())
{
requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(attrNameOrOID));
final AttributeTypeDefinition at =
existingSchema.getAttributeType(attrNameOrOID);
if (at == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_UNDEFINED_REQUIRED_ATTR.get(
objectClassString, schemaFile.getAbsolutePath(),
attrNameOrOID));
}
}
else
{
requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(at.getOID()));
for (final String name : at.getNames())
{
requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(name));
}
}
}
for (final String attrNameOrOID : objectClass.getOptionalAttributes())
{
if (requiredAttrNamesAndOIDs.contains(
StaticUtils.toLowerCase(attrNameOrOID)))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_AT_REQ_AND_OPT.get(
objectClassString, schemaFile.getAbsolutePath(),
attrNameOrOID));
}
final AttributeTypeDefinition at =
existingSchema.getAttributeType(attrNameOrOID);
if ((at == null) &&
(! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE)))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_UNDEFINED_OPTIONAL_ATTR.get(
objectClassString, schemaFile.getAbsolutePath(),
attrNameOrOID));
}
}
// Make sure that the object class isn't already defined.
boolean isDuplicate = false;
if (! allowRedefiningElements)
{
final String lowerOID = StaticUtils.toLowerCase(objectClass.getOID());
ObjectClassDefinition existingDefinition =
objectClassMap.get(lowerOID);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getObjectClass(lowerOID);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_ALREADY_DEFINED_WITH_OID.get(
objectClassString, schemaFile.getAbsolutePath(),
existingDefinition.toString()));
isDuplicate = true;
}
if (! isDuplicate)
{
for (final String name : objectClass.getNames())
{
final String lowerName = StaticUtils.toLowerCase(name);
existingDefinition = objectClassMap.get(lowerName);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getObjectClass(lowerName);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_ALREADY_DEFINED_WITH_NAME.get(
objectClassString, schemaFile.getAbsolutePath(),
name, existingDefinition.toString()));
isDuplicate = true;
break;
}
}
}
}
// Add the object class to the map so it can be referenced by subordinate
// classes.
if (! isDuplicate)
{
objectClassMap.put(StaticUtils.toLowerCase(objectClass.getOID()),
objectClass);
for (final String name : objectClass.getNames())
{
objectClassMap.put(StaticUtils.toLowerCase(name), objectClass);
}
}
}
}
/**
* Retrieves the definitions for the superior object classes for the provided
* object class.
*
* @param schemaFile
* The file from which the object class was read. It must not be
* {@code null}.
* @param objectClass
* The object class for which to retrieve the superior classes.
* It must not be {@code null}.
* @param objectClassMap
* A map of the object class definitions that have already been
* parsed from the same file. It must not be {@code null} (but
* may be empty), and it must be updatable.
* @param existingSchema
* An existing schema that has already been read (e.g., from
* earlier schema files). It must not be {@code null} .
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateSuperiorObjectClasses(@NotNull final File schemaFile,
@NotNull final ObjectClassDefinition objectClass,
@NotNull final Map objectClassMap,
@NotNull final Schema existingSchema,
@NotNull final List errorMessages)
{
// If the object class does not define any superior classes, then determine
// if that's okay.
final String[] superiorClassNamesOrOIDs =
objectClass.getSuperiorClasses();
if (superiorClassNamesOrOIDs.length == 0)
{
if (! allowStructuralObjectClassWithoutSuperior)
{
final ObjectClassType type = objectClass.getObjectClassType();
if (type == null)
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_NO_SUP_NULL_TYPE.get(
objectClass.toString(), schemaFile.getAbsolutePath()));
}
else if (type == ObjectClassType.STRUCTURAL)
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_NO_SUP_STRUCTURAL_TYPE.get(
objectClass.toString(), schemaFile.getAbsolutePath()));
}
}
return;
}
// If the object class has multiple superior classes, then determine if
// that's okay.
if ((superiorClassNamesOrOIDs.length > 1) &&
(! allowMultipleSuperiorObjectClasses))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_MULTIPLE_SUP.get(
objectClass.toString(), schemaFile.getAbsolutePath()));
}
// Make sure that we can retrieve all of the superior classes.
final Map superiorClasses =
new LinkedHashMap<>();
for (final String nameOrOID : superiorClassNamesOrOIDs)
{
final String lowerNameOrOID = StaticUtils.toLowerCase(nameOrOID);
ObjectClassDefinition superiorClass = objectClassMap.get(lowerNameOrOID);
if (superiorClass == null)
{
superiorClass = existingSchema.getObjectClass(lowerNameOrOID);
}
if (superiorClass == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.OBJECT_CLASS))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_OC_UNDEFINED_SUP.get(
objectClass.toString(), schemaFile.getAbsolutePath(),
nameOrOID));
}
}
else
{
superiorClasses.put(nameOrOID, superiorClass);
}
}
// If we should verify the superior relationships, then do that now.
if ((! allowInvalidObjectClassInheritance) && (! superiorClasses.isEmpty()))
{
if (objectClass.getObjectClassType() == null)
{
for (final Map.Entry e :
superiorClasses.entrySet())
{
if (e.getValue().getObjectClassType() == ObjectClassType.AUXILIARY)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_IMPLIED_STRUCTURAL_SUP_OF_AUXILIARY.
get(objectClass.toString(), schemaFile.getAbsolutePath(),
e.getKey()));
break;
}
}
}
else
{
switch (objectClass.getObjectClassType())
{
case STRUCTURAL:
for (final Map.Entry e :
superiorClasses.entrySet())
{
if (e.getValue().getObjectClassType() ==
ObjectClassType.AUXILIARY)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_STRUCTURAL_SUP_OF_AUXILIARY.get(
objectClass.toString(), schemaFile.getAbsolutePath(),
e.getKey()));
break;
}
}
break;
case AUXILIARY:
for (final Map.Entry e :
superiorClasses.entrySet())
{
if (e.getValue().getObjectClassType() ==
ObjectClassType.STRUCTURAL)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_AUXILIARY_SUP_OF_STRUCTURAL.get(
objectClass.toString(), schemaFile.getAbsolutePath(),
e.getKey()));
break;
}
}
break;
case ABSTRACT:
for (final Map.Entry e :
superiorClasses.entrySet())
{
if (e.getValue().getObjectClassType() ==
ObjectClassType.STRUCTURAL)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_ABSTRACT_SUP_OF_STRUCTURAL.get(
objectClass.toString(), schemaFile.getAbsolutePath(),
e.getKey()));
break;
}
else if (e.getValue().getObjectClassType() ==
ObjectClassType.AUXILIARY)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_OC_ABSTRACT_SUP_OF_AUXILIARY.get(
objectClass.toString(), schemaFile.getAbsolutePath(),
e.getKey()));
break;
}
}
break;
}
}
}
}
/**
* Validates any name form definitions contained in the provided schema entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param nameFormsByNameOrOID
* A map of the name form definitions that have already
* been parsed from the same file, indexed by OID and names. It
* must not be {@code null} (but may be empty), and it must be
* updatable.
* @param nameFormsByOC
* A map of the name form definitions that have already
* been parsed from the same file, indexed by structural object
* class. It must not be {@code null} (but may be empty), and it
* must be updatable.
* @param existingSchema
* An existing schema that has already been read (e.g., from
* earlier schema files). It must not be {@code null}.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateNameForms(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@NotNull final Map nameFormsByNameOrOID,
@NotNull final Map
nameFormsByOC,
@NotNull final Schema existingSchema,
@NotNull final List errorMessages)
{
for (final String nameFormString :
schemaEntry.getAttributeValues(Schema.ATTR_NAME_FORM))
{
// If name forms aren't allowed, then report an error without doing
// anything else.
if (! allowedSchemaElementTypes.contains(SchemaElementType.NAME_FORM))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_NOT_ALLOWED.get(
schemaFile.getAbsolutePath(), nameFormString));
continue;
}
// Make sure that we can parse the name form definition.
final NameFormDefinition nameForm;
try
{
nameForm = new NameFormDefinition(nameFormString);
}
catch (final LDAPException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_NF.get(
nameFormString, schemaFile.getAbsolutePath(), e.getMessage()));
continue;
}
// Make sure that the name form has a valid numeric OID.
try
{
validateOID(nameForm.getOID(), nameForm.getNames());
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_INVALID_OID.get(
nameFormString, schemaFile.getAbsolutePath(), e.getMessage()));
}
// Make sure that all of the names are valid.
if ((nameForm.getNames().length == 0) && (! allowElementsWithoutNames))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_NO_NAME.get(nameFormString,
schemaFile.getAbsolutePath()));
}
for (final String name : nameForm.getNames())
{
try
{
validateName(name);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_INVALID_NAME.get(
nameFormString, schemaFile.getAbsolutePath(), name,
e.getMessage()));
}
}
// If the name form has a description, then make sure it's not empty.
if (! allowEmptyDescription)
{
final String description = nameForm.getDescription();
if ((description != null) && description.isEmpty())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_EMPTY_DESCRIPTION.get(
nameFormString, schemaFile.getAbsolutePath()));
}
}
// If the name form is declared obsolete, then make sure that's
// allowed.
if (nameForm.isObsolete() && (! allowObsoleteElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_OBSOLETE.get(
nameFormString, schemaFile.getAbsolutePath()));
}
// Make sure that the structural object class is defined and is defined as
// structural.
final String structuralClassNameOrOID = nameForm.getStructuralClass();
final ObjectClassDefinition structuralClass =
existingSchema.getObjectClass(structuralClassNameOrOID);
if (structuralClass == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.OBJECT_CLASS))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_UNDEFINED_OC.get(
nameFormString, schemaFile.getAbsolutePath(),
structuralClassNameOrOID));
}
}
else
{
if ((structuralClass.getObjectClassType() != null) &&
(structuralClass.getObjectClassType() !=
ObjectClassType.STRUCTURAL))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_OC_NOT_STRUCTURAL.get(
nameFormString, schemaFile.getAbsolutePath(),
structuralClassNameOrOID));
}
}
// Make sure that all of the required attribute types are defined and
// permitted by the structural class.
final Set requiredAttrNamesAndOIDs = new HashSet<>();
for (final String attrNameOrOID : nameForm.getRequiredAttributes())
{
requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(attrNameOrOID));
final AttributeTypeDefinition attrType =
existingSchema.getAttributeType(attrNameOrOID);
if (attrType == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_UNDEFINED_REQ_ATTR.get(
nameFormString, schemaFile.getAbsolutePath(),
attrNameOrOID));
}
}
else
{
requiredAttrNamesAndOIDs.add(
StaticUtils.toLowerCase(attrType.getOID()));
for (final String name : attrType.getNames())
{
requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(name));
}
if ((structuralClass != null) &&
(! (structuralClass.getRequiredAttributes(existingSchema, true).
contains(attrType) ||
structuralClass.getOptionalAttributes(existingSchema, true).
contains(attrType))))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_NF_REQ_ATTR_NOT_PERMITTED.get(
nameFormString, schemaFile.getAbsolutePath(),
attrNameOrOID, structuralClassNameOrOID));
}
}
}
// Make sure that all of the optional attribute types are defined and
// permitted by the structural class. Also, make sure that none of them
// also appear in the set of required attributes.
for (final String attrNameOrOID : nameForm.getOptionalAttributes())
{
if (requiredAttrNamesAndOIDs.contains(
StaticUtils.toLowerCase(attrNameOrOID)))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_ATTR_REQ_AND_OPT.get(
nameFormString, schemaFile.getAbsolutePath(), attrNameOrOID));
}
final AttributeTypeDefinition attrType =
existingSchema.getAttributeType(attrNameOrOID);
if (attrType == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_NF_UNDEFINED_OPT_ATTR.get(
nameFormString, schemaFile.getAbsolutePath(),
attrNameOrOID));
}
}
}
// Make sure that the name form isn't already defined by OID, name, or
// structural class.
boolean isDuplicate = false;
if (! allowRedefiningElements)
{
final String lowerOID = StaticUtils.toLowerCase(nameForm.getOID());
NameFormDefinition existingDefinition =
nameFormsByNameOrOID.get(lowerOID);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getNameFormByName(lowerOID);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_NF_ALREADY_DEFINED_WITH_OID.get(
nameFormString, schemaFile.getAbsolutePath(),
existingDefinition.toString()));
isDuplicate = true;
}
if (! isDuplicate)
{
for (final String name : nameForm.getNames())
{
final String lowerName = StaticUtils.toLowerCase(name);
existingDefinition = nameFormsByNameOrOID.get(lowerName);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getNameFormByName(lowerName);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_NF_ALREADY_DEFINED_WITH_NAME.get(
nameFormString, schemaFile.getAbsolutePath(),
name, existingDefinition.toString()));
isDuplicate = true;
break;
}
}
}
if ((! isDuplicate) && (structuralClass != null))
{
existingDefinition = nameFormsByOC.get(structuralClass);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getNameFormByObjectClass(
structuralClassNameOrOID);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_NF_ALREADY_DEFINED_WITH_OC.get(
nameFormString, schemaFile.getAbsolutePath(),
structuralClassNameOrOID, existingDefinition.toString()));
isDuplicate = true;
}
}
}
// Add the name form to the maps so we can detect conflicts with later
// name forms.
if (! isDuplicate)
{
nameFormsByNameOrOID.put(StaticUtils.toLowerCase(nameForm.getOID()),
nameForm);
for (final String name : nameForm.getNames())
{
nameFormsByNameOrOID.put(StaticUtils.toLowerCase(name), nameForm);
}
if (structuralClass != null)
{
nameFormsByOC.put(structuralClass, nameForm);
}
}
}
}
/**
* Validates any DIT content rule definitions contained in the provided schema
* entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param dcrMap
* A map of the DIT content rule definitions that have already
* been parsed from the same file. It must not be {@code null}
* (but may be empty), and it must be updatable.
* @param existingSchema
* An existing schema that has already been read (e.g., from
* earlier schema files). It must not be {@code null}.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateDITContentRules(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@NotNull final Map dcrMap,
@NotNull final Schema existingSchema,
@NotNull final List errorMessages)
{
for (final String dcrString :
schemaEntry.getAttributeValues(Schema.ATTR_DIT_CONTENT_RULE))
{
// If DIT content rules aren't allowed, then report an error without
// doing anything else.
if (! allowedSchemaElementTypes.contains(
SchemaElementType.DIT_CONTENT_RULE))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_NOT_ALLOWED.get(
schemaFile.getAbsolutePath(), dcrString));
continue;
}
// Make sure that we can parse the DIT content rule definition.
final DITContentRuleDefinition dcr;
try
{
dcr = new DITContentRuleDefinition(dcrString);
}
catch (final LDAPException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_DCR.get(
dcrString, schemaFile.getAbsolutePath(), e.getMessage()));
continue;
}
// Make sure that the DIT content rule has a valid numeric OID.
try
{
validateOID(dcr.getOID(), dcr.getNames());
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_INVALID_OID.get(
dcrString, schemaFile.getAbsolutePath(), e.getMessage()));
}
// The DIT content rule's numeric OID must reference a structural
// object class.
final ObjectClassDefinition structuralObjectClass =
existingSchema.getObjectClass(dcr.getOID());
if (structuralObjectClass == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.OBJECT_CLASS))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_UNKNOWN_STRUCTURAL_CLASS.get(dcrString,
schemaFile.getAbsolutePath(), dcr.getOID()));
}
}
else
{
if ((structuralObjectClass.getObjectClassType() != null) &&
(structuralObjectClass.getObjectClassType() !=
ObjectClassType.STRUCTURAL))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_STRUCTURAL_CLASS_NOT_STRUCTURAL.get(
dcrString, schemaFile.getAbsolutePath(), dcr.getOID(),
structuralObjectClass.toString()));
}
}
// Make sure that all of the names are valid.
if ((dcr.getNames().length == 0) &&
(! allowElementsWithoutNames))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_NO_NAME.get(
dcrString, schemaFile.getAbsolutePath()));
}
for (final String name : dcr.getNames())
{
try
{
validateName(name);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_INVALID_NAME.get(
dcrString, schemaFile.getAbsolutePath(), name,
e.getMessage()));
}
}
// If the DIT content rule has a description, then make sure it's not
// empty.
if (! allowEmptyDescription)
{
final String description = dcr.getDescription();
if ((description != null) && description.isEmpty())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_EMPTY_DESCRIPTION.get(
dcrString, schemaFile.getAbsolutePath()));
}
}
// If the DIT content rule is declared obsolete, then make sure that's
// allowed.
if (dcr.isObsolete() && (! allowObsoleteElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_OBSOLETE.get(
dcrString, schemaFile.getAbsolutePath()));
}
// If there are any auxiliary classes, make sure they are defined and
// are auxiliary.
for (final String auxClassNameOrOID : dcr.getAuxiliaryClasses())
{
final ObjectClassDefinition auxClass =
existingSchema.getObjectClass(auxClassNameOrOID);
if (auxClass == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.OBJECT_CLASS))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_UNDEFINED_AUX_CLASS.get(
dcrString, schemaFile.getAbsolutePath(), auxClassNameOrOID));
}
}
else if (auxClass.getObjectClassType() != ObjectClassType.AUXILIARY)
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_AUX_CLASS_NOT_AUX.get(
dcrString, schemaFile.getAbsolutePath(),
auxClass.toString()));
}
}
// If there are any required attribute types, then make sure they are
// defined.
final Set requiredAttrNamesAndOIDs = new HashSet<>();
for (final String attrNameOrOID : dcr.getRequiredAttributes())
{
requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(attrNameOrOID));
final AttributeTypeDefinition at =
existingSchema.getAttributeType(attrNameOrOID);
if (at == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_UNDEFINED_REQUIRED_ATTR.get(
dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
}
}
else
{
requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(at.getOID()));
for (final String name : at.getNames())
{
requiredAttrNamesAndOIDs.add(StaticUtils.toLowerCase(name));
}
}
}
// If there are any optional attribute types, then make sure they are
// defined. Also, make sure they are also not listed as required.
final Set optionalAttrNamesAndOIDs = new HashSet<>();
for (final String attrNameOrOID : dcr.getOptionalAttributes())
{
final String lowerNameOrOID = StaticUtils.toLowerCase(attrNameOrOID);
optionalAttrNamesAndOIDs.add(lowerNameOrOID);
if (requiredAttrNamesAndOIDs.contains(lowerNameOrOID))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_ATTR_REQ_AND_OPT.get(
dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
}
final AttributeTypeDefinition at =
existingSchema.getAttributeType(lowerNameOrOID);
if (at == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_UNDEFINED_OPTIONAL_ATTR.get(
dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
}
}
else
{
optionalAttrNamesAndOIDs.add(StaticUtils.toLowerCase(at.getOID()));
for (final String name : at.getNames())
{
optionalAttrNamesAndOIDs.add(StaticUtils.toLowerCase(name));
}
}
}
// If there are any prohibited attribute types, then make sure they are
// defined. Also, make sure they are not listed as required or optional,
// and make sure they are not required by the structural or any of the
// auxiliary classes.
for (final String attrNameOrOID : dcr.getProhibitedAttributes())
{
final String lowerNameOrOID = StaticUtils.toLowerCase(attrNameOrOID);
if (requiredAttrNamesAndOIDs.contains(lowerNameOrOID))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_ATTR_REQ_AND_NOT.get(
dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
}
if (optionalAttrNamesAndOIDs.contains(lowerNameOrOID))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DCR_ATTR_OPT_AND_NOT.get(
dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
}
final AttributeTypeDefinition at =
existingSchema.getAttributeType(lowerNameOrOID);
if (at == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_UNDEFINED_PROHIBITED_ATTR.get(
dcrString, schemaFile.getAbsolutePath(), attrNameOrOID));
}
}
else
{
if (structuralObjectClass != null)
{
if (structuralObjectClass.getRequiredAttributes(existingSchema,
true).contains(at))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_PROHIBITED_ATTR_REQUIRED_BY_STRUCT.
get(dcrString, schemaFile.getAbsolutePath(),
attrNameOrOID));
}
}
for (final String auxClassNameOrOID : dcr.getAuxiliaryClasses())
{
final ObjectClassDefinition auxClass =
existingSchema.getObjectClass(auxClassNameOrOID);
if ((auxClass != null) &&
auxClass.getRequiredAttributes(existingSchema, true).contains(
at))
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_PROHIBITED_ATTR_REQUIRED_BY_AUX.
get(dcrString, schemaFile.getAbsolutePath(),
attrNameOrOID, auxClassNameOrOID));
}
}
}
}
// Make sure that the DIT content rule isn't already defined.
boolean isDuplicate = false;
if (! allowRedefiningElements)
{
final String lowerOID = StaticUtils.toLowerCase(dcr.getOID());
DITContentRuleDefinition existingDefinition = dcrMap.get(lowerOID);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getDITContentRule(lowerOID);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_ALREADY_DEFINED_WITH_OID.get(
dcrString, schemaFile.getAbsolutePath(),
existingDefinition.toString()));
isDuplicate = true;
}
if (! isDuplicate)
{
for (final String name : dcr.getNames())
{
final String lowerName = StaticUtils.toLowerCase(name);
existingDefinition = dcrMap.get(lowerName);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getDITContentRule(lowerName);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DCR_ALREADY_DEFINED_WITH_NAME.get(
dcrString, schemaFile.getAbsolutePath(),
name, existingDefinition.toString()));
isDuplicate = true;
break;
}
}
}
}
// Add the DIT content rule to the map so it can be used to detect
// duplicates.
if (! isDuplicate)
{
dcrMap.put(StaticUtils.toLowerCase(dcr.getOID()), dcr);
for (final String name : dcr.getNames())
{
dcrMap.put(StaticUtils.toLowerCase(name), dcr);
}
}
}
}
/**
* Validates any DIT structure rule definitions contained in the provided
* schema entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param dsrIDAndNameMap
* A map of the DIT structure rule definitions that have already
* been parsed from the same file, indexed by rule ID and name.
* It must not be {@code null} (but may be empty), and it must be
* updatable.
* @param dsrNFMap
* A map of the DIT structure rule definitions that have already
* been parsed from the same file, indexed by name form
* definition. It must not be {@code null} (but may be empty),
* and it must be updatable.
* @param existingSchema
* An existing schema that has already been read (e.g., from
* earlier schema files). It must not be {@code null}.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateDITStructureRules(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@NotNull final Map dsrIDAndNameMap,
@NotNull final Map
dsrNFMap,
@NotNull final Schema existingSchema,
@NotNull final List errorMessages)
{
for (final String dsrString :
schemaEntry.getAttributeValues(Schema.ATTR_DIT_STRUCTURE_RULE))
{
// If DIT structure rules aren't allowed, then report an error without
// doing anything else.
if (! allowedSchemaElementTypes.contains(
SchemaElementType.DIT_STRUCTURE_RULE))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_NOT_ALLOWED.get(
schemaFile.getAbsolutePath(), dsrString));
continue;
}
// Make sure that we can parse the DIT structure rule definition.
final DITStructureRuleDefinition dsr;
try
{
dsr = new DITStructureRuleDefinition(dsrString);
}
catch (final LDAPException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_DSR.get(
dsrString, schemaFile.getAbsolutePath(), e.getMessage()));
continue;
}
// Make sure that all of the names are valid.
if ((dsr.getNames().length == 0) &&
(! allowElementsWithoutNames))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_NO_NAME.get(
dsrString, schemaFile.getAbsolutePath()));
}
for (final String name : dsr.getNames())
{
try
{
validateName(name);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_INVALID_NAME.get(
dsrString, schemaFile.getAbsolutePath(), name,
e.getMessage()));
}
}
// If the DIT structure rule has a description, then make sure it's not
// empty.
if (! allowEmptyDescription)
{
final String description = dsr.getDescription();
if ((description != null) && description.isEmpty())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_EMPTY_DESCRIPTION.get(
dsrString, schemaFile.getAbsolutePath()));
}
}
// If the DIT content rule is declared obsolete, then make sure that's
// allowed.
if (dsr.isObsolete() && (! allowObsoleteElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_OBSOLETE.get(
dsrString, schemaFile.getAbsolutePath()));
}
// Make sure that the name form is defined.
final String nameFormNameOrOID = dsr.getNameFormID();
final NameFormDefinition nameForm =
existingSchema.getNameFormByName(nameFormNameOrOID);
if ((nameForm == null) &&
(! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.NAME_FORM)))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_UNDEFINED_NF.get(dsrString,
schemaFile.getAbsolutePath(), nameFormNameOrOID));
}
// If there are any superior rule IDs, then make sure they are defined.
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.DIT_STRUCTURE_RULE))
{
for (final int superiorRuleID : dsr.getSuperiorRuleIDs())
{
final DITStructureRuleDefinition superiorDSR =
dsrIDAndNameMap.get(String.valueOf(superiorRuleID));
if (superiorDSR == null)
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_DSR_UNDEFINED_SUP.get(
dsrString, schemaFile.getAbsolutePath(), superiorRuleID));
}
}
}
// Make sure that the DIT structure rule isn't already defined.
boolean isDuplicate = false;
if (! allowRedefiningElements)
{
DITStructureRuleDefinition existingDefinition =
dsrIDAndNameMap.get(String.valueOf(dsr.getRuleID()));
if (existingDefinition == null)
{
existingDefinition =
existingSchema.getDITStructureRuleByID(dsr.getRuleID());
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DSR_ALREADY_DEFINED_WITH_ID.get(
dsrString, schemaFile.getAbsolutePath(),
existingDefinition.toString()));
isDuplicate = true;
}
if (! isDuplicate)
{
for (final String name : dsr.getNames())
{
final String lowerName = StaticUtils.toLowerCase(name);
existingDefinition = dsrIDAndNameMap.get(lowerName);
if (existingDefinition == null)
{
existingDefinition =
existingSchema.getDITStructureRuleByName(lowerName);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DSR_ALREADY_DEFINED_WITH_NAME.get(
dsrString, schemaFile.getAbsolutePath(),
name, existingDefinition.toString()));
isDuplicate = true;
break;
}
}
}
if ((! isDuplicate) && (nameForm != null))
{
existingDefinition = dsrNFMap.get(nameForm);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getDITStructureRuleByNameForm(
nameFormNameOrOID);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_DSR_ALREADY_DEFINED_WITH_NF.get(
dsrString, schemaFile.getAbsolutePath(),
nameFormNameOrOID, existingDefinition.toString()));
isDuplicate = true;
}
}
}
// Add the DIT content rule to the map so it can be used to detect
// duplicates.
if (! isDuplicate)
{
dsrIDAndNameMap.put(String.valueOf(dsr.getRuleID()), dsr);
for (final String name : dsr.getNames())
{
dsrIDAndNameMap.put(StaticUtils.toLowerCase(name), dsr);
}
if (nameForm != null)
{
dsrNFMap.put(nameForm, dsr);
}
}
}
}
/**
* Validates any matching rule use definitions contained in the provided
* schema entry.
*
* @param schemaEntry
* The entry containing the schema definitions to validate. It
* must not be {@code null}.
* @param schemaFile
* The file from which the schema entry was read. It must not be
* {@code null}.
* @param mruMap
* A map of the matching rule use definitions that have already
* been parsed from the same file. It must not be {@code null}
* (but may be empty), and it must be updatable.
* @param existingSchema
* An existing schema that has already been read (e.g., from
* earlier schema files). It must not be {@code null}.
* @param errorMessages
* A list that will be updated with error messages about any
* problems identified during processing. It must not be
* {@code null}, and it must be updatable.
*/
private void validateMatchingRuleUses(@NotNull final Entry schemaEntry,
@NotNull final File schemaFile,
@NotNull final Map mruMap,
@NotNull final Schema existingSchema,
@NotNull final List errorMessages)
{
for (final String mruString :
schemaEntry.getAttributeValues(Schema.ATTR_MATCHING_RULE_USE))
{
// If matching rule uses aren't allowed, then report an error without
// doing anything else.
if (! allowedSchemaElementTypes.contains(
SchemaElementType.MATCHING_RULE_USE))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_NOT_ALLOWED.get(
schemaFile.getAbsolutePath(), mruString));
continue;
}
// Make sure that we can parse the matching rule use definition.
final MatchingRuleUseDefinition mru;
try
{
mru = new MatchingRuleUseDefinition(mruString);
}
catch (final LDAPException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_CANNOT_PARSE_MRU.get(
mruString, schemaFile.getAbsolutePath(), e.getMessage()));
continue;
}
// Make sure that the matching rule use has a valid numeric OID.
try
{
validateOID(mru.getOID(), mru.getNames());
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_INVALID_OID.get(
mruString, schemaFile.getAbsolutePath(), e.getMessage()));
}
// Make sure that the matching rule use OID references a defined matching
// rule.
MatchingRuleDefinition matchingRule =
matchingRuleMap.get( StaticUtils.toLowerCase(mru.getOID()));
if (matchingRule == null)
{
matchingRule = existingSchema.getMatchingRule(mru.getOID());
}
if ((matchingRule == null) &&
(! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.MATCHING_RULE)))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_UNDEFINED_MR.get(
mruString, schemaFile.getAbsolutePath(), mru.getOID()));
}
// Make sure that all of the names are valid.
if ((mru.getNames().length == 0) &&
(! allowElementsWithoutNames))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_NO_NAME.get(
mruString, schemaFile.getAbsolutePath()));
}
for (final String name : mru.getNames())
{
try
{
validateName(name);
}
catch (final ParseException e)
{
Debug.debugException(e);
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_INVALID_NAME.get(
mruString, schemaFile.getAbsolutePath(), name,
e.getMessage()));
}
}
// If the matching rule use has a description, then make sure it's not
// empty.
if (! allowEmptyDescription)
{
final String description = mru.getDescription();
if ((description != null) && description.isEmpty())
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_EMPTY_DESCRIPTION.get(
mruString, schemaFile.getAbsolutePath()));
}
}
// If the matching rule use is declared obsolete, then make sure that's
// allowed.
if (mru.isObsolete() && (! allowObsoleteElements))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_OBSOLETE.get(
mruString, schemaFile.getAbsolutePath()));
}
// Make sure that all of the referenced attribute types are defined in the
// schema.
final Set applicableTypes = new HashSet<>();
for (final String attrNameOrOID : mru.getApplicableAttributeTypes())
{
final AttributeTypeDefinition at =
existingSchema.getAttributeType(attrNameOrOID);
if (at == null)
{
if (! allowReferencesToUndefinedElementTypes.contains(
SchemaElementType.ATTRIBUTE_TYPE))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_UNDEFINED_AT.get(
mruString, schemaFile.getAbsolutePath(), attrNameOrOID));
}
}
else
{
applicableTypes.add(at);
}
}
// Examine the schema to determine whether there are any attribute types
// that use the associated matching rule but aren't in the list of
// applicable types.
if (matchingRule != null)
{
for (final AttributeTypeDefinition at :
existingSchema.getAttributeTypes())
{
if (applicableTypes.contains(at))
{
continue;
}
final String eqMR = at.getEqualityMatchingRule();
if ((eqMR != null) && matchingRule.hasNameOrOID(eqMR))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_PROHIBITS_AT_EQ.get(
at.toString(), eqMR, mruString, schemaFile.getAbsolutePath()));
}
final String ordMR = at.getOrderingMatchingRule();
if ((ordMR != null) && matchingRule.hasNameOrOID(ordMR))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_PROHIBITS_AT_ORD.get(
at.toString(), ordMR, mruString,
schemaFile.getAbsolutePath()));
}
final String subMR = at.getSubstringMatchingRule();
if ((subMR != null) && matchingRule.hasNameOrOID(subMR))
{
errorMessages.add(ERR_SCHEMA_VALIDATOR_MRU_PROHIBITS_AT_SUB.get(
at.toString(), subMR, mruString,
schemaFile.getAbsolutePath()));
}
}
}
// Make sure that the matching rule use isn't already defined.
boolean isDuplicate = false;
if (! allowRedefiningElements)
{
final String lowerOID = StaticUtils.toLowerCase(mru.getOID());
MatchingRuleUseDefinition existingDefinition = mruMap.get(lowerOID);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getMatchingRuleUse(lowerOID);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_MRU_ALREADY_DEFINED_WITH_OID.get(
mruString, schemaFile.getAbsolutePath(),
existingDefinition.toString()));
isDuplicate = true;
}
if (! isDuplicate)
{
for (final String name : mru.getNames())
{
final String lowerName = StaticUtils.toLowerCase(name);
existingDefinition = mruMap.get(lowerName);
if (existingDefinition == null)
{
existingDefinition = existingSchema.getMatchingRuleUse(lowerName);
}
if (existingDefinition != null)
{
errorMessages.add(
ERR_SCHEMA_VALIDATOR_MRU_ALREADY_DEFINED_WITH_NAME.get(
mruString, schemaFile.getAbsolutePath(),
name, existingDefinition.toString()));
isDuplicate = true;
break;
}
}
}
}
// Add the matching rule use to the map so it can be used to detect
// duplicates.
if (! isDuplicate)
{
mruMap.put(StaticUtils.toLowerCase(mru.getOID()), mru);
for (final String name : mru.getNames())
{
mruMap.put(StaticUtils.toLowerCase(name), mru);
}
}
}
}
/**
* Ensures that the provided object identifier is valid, within the
* constraints of the schema validator.
*
* @param oid The object identifier to validate. It must not be
* {@code null}.
* @param names The set of names for the schema element. It may be
* {@code null} or empty if the element does not have any
* names.
*
* @throws ParseException If the provided OID is not valid.
*/
private void validateOID(@NotNull final String oid,
@Nullable final String[] names)
throws ParseException
{
try
{
OID.parseNumericOID(oid, useStrictOIDValidation);
}
catch (final ParseException e)
{
Debug.debugException(e);
boolean acceptable = false;
if (allowNonNumericOIDsUsingName && (names != null))
{
for (final String name : names)
{
if (oid.equalsIgnoreCase(name + "-oid"))
{
acceptable = true;
break;
}
}
}
if ((! acceptable ) && allowNonNumericOIDsNotUsingName)
{
acceptable = true;
}
if (! acceptable)
{
throw e;
}
}
}
/**
* Ensures that the provided name is valid for a schema element, within the
* constraints of the schema validator.
*
* @param name The name to validate. It must not be {@code null}.
*
* @throws ParseException If the provided name is not valid.
*/
void validateName(@NotNull final String name)
throws ParseException
{
if (name.isEmpty())
{
throw new ParseException(ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_EMPTY.get(),
0);
}
final char firstChar = name.charAt(0);
if (((firstChar >= 'a') && (firstChar <= 'z')) ||
((firstChar >= 'A') && (firstChar <= 'Z')))
{
// This is always okay.
}
else if ((firstChar >= '0') && (firstChar <= '9'))
{
if (allowNamesWithInitialDigit)
{
// This is technically illegal, but we'll allow it.
}
else
{
throw new ParseException(
ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_DOES_NOT_START_WITH_LETTER.get(),
0);
}
}
else if (firstChar == '-')
{
if (allowNamesWithInitialHyphen)
{
// This is technically illegal, but we'll allow it.
}
else
{
throw new ParseException(
ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_DOES_NOT_START_WITH_LETTER.get(),
0);
}
}
else if (firstChar == '_')
{
if (allowNamesWithUnderscore)
{
// This is technically illegal, but we'll allow it.
}
else
{
throw new ParseException(
ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_DOES_NOT_START_WITH_LETTER.get(),
0);
}
}
else
{
throw new ParseException(
ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_DOES_NOT_START_WITH_LETTER.get(),
0);
}
for (int i = 1; i < name.length(); i++)
{
final char subsequentChar = name.charAt(i);
if (((subsequentChar >= 'a') && (subsequentChar <= 'z')) ||
((subsequentChar >= 'A') && (subsequentChar <= 'Z')) ||
((subsequentChar >= '0') && (subsequentChar <= '9')) ||
(subsequentChar == '-'))
{
// This is always okay.
}
else if ((subsequentChar == '_') && allowNamesWithUnderscore)
{
// This is technically illegal, but we'll allow it.
}
else
{
throw new ParseException(
ERR_SCHEMA_VALIDATOR_ELEMENT_NAME_ILLEGAL_CHARACTER.get(
subsequentChar, i),
i);
}
}
}
}