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