Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2020-2024 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2020-2024 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-2024 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);
}
}
}
}