001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-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) 2008-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.Serializable; 041import java.util.ArrayList; 042import java.util.Collection; 043import java.util.Collections; 044import java.util.HashSet; 045import java.util.Iterator; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049import java.util.TreeMap; 050import java.util.concurrent.ConcurrentHashMap; 051import java.util.concurrent.atomic.AtomicLong; 052import java.util.concurrent.atomic.AtomicReference; 053import java.util.regex.Pattern; 054 055import com.unboundid.asn1.ASN1OctetString; 056import com.unboundid.ldap.matchingrules.MatchingRule; 057import com.unboundid.ldap.sdk.Attribute; 058import com.unboundid.ldap.sdk.Entry; 059import com.unboundid.ldap.sdk.LDAPException; 060import com.unboundid.ldap.sdk.RDN; 061import com.unboundid.util.Debug; 062import com.unboundid.util.NotNull; 063import com.unboundid.util.Nullable; 064import com.unboundid.util.StaticUtils; 065import com.unboundid.util.ThreadSafety; 066import com.unboundid.util.ThreadSafetyLevel; 067import com.unboundid.util.Validator; 068 069import static com.unboundid.ldap.sdk.schema.SchemaMessages.*; 070 071 072 073/** 074 * This class provides a mechanism for validating entries against a schema. It 075 * provides the ability to customize the types of validation to perform, and can 076 * collect information about the entries that fail validation to provide a 077 * summary of the problems encountered. 078 * <BR><BR> 079 * The types of validation that may be performed for each entry include: 080 * <UL> 081 * <LI>Ensure that the entry has a valid DN.</LI> 082 * <LI>Ensure that all attribute values used in the entry's RDN are also 083 * present in the entry.</LI> 084 * <LI>Ensure that the entry has exactly one structural object class.</LI> 085 * <LI>Ensure that all of the object classes for the entry are defined in the 086 * schema.</LI> 087 * <LI>Ensure that all of the auxiliary classes for the entry are allowed by 088 * the DIT content rule for the entry's structural object class (if such a 089 * DIT content rule is defined).</LI> 090 * <LI>Ensure that all attributes contained in the entry are defined in the 091 * schema.</LI> 092 * <LI>Ensure that all attributes required by the entry's object classes or 093 * DIT content rule (if defined) are present in the entry.</LI> 094 * <LI>Ensure that all of the user attributes contained in the entry are 095 * allowed by the entry's object classes or DIT content rule (if 096 * defined).</LI> 097 * <LI>Ensure that all attribute values conform to the requirements of the 098 * associated attribute syntax.</LI> 099 * <LI>Ensure that all attributes with multiple values are defined as 100 * multi-valued in the associated schema.</LI> 101 * <LI>If there is a name form associated with the entry's structural object 102 * class, then ensure that the entry's RDN satisfies its constraints.</LI> 103 * </UL> 104 * All of these forms of validation will be performed by default, but individual 105 * types of validation may be enabled or disabled. 106 * <BR><BR> 107 * This class will not make any attempt to validate compliance with DIT 108 * structure rules, nor will it check the OBSOLETE field for any of the schema 109 * elements. In addition, attempts to validate whether attribute values 110 * conform to the syntax for the associated attribute type may only be 111 * completely accurate for syntaxes supported by the LDAP SDK. 112 * <BR><BR> 113 * This class is largely threadsafe, and the {@link EntryValidator#entryIsValid} 114 * is designed so that it can be invoked concurrently by multiple threads. 115 * Note, however, that it is not recommended that the any of the other methods 116 * in this class be used while any threads are running the {@code entryIsValid} 117 * method because changing the configuration or attempting to retrieve retrieve 118 * information may yield inaccurate or inconsistent results. 119 */ 120@ThreadSafety(level=ThreadSafetyLevel.MOSTLY_THREADSAFE) 121public final class EntryValidator 122 implements Serializable 123{ 124 /** 125 * The serial version UID for this serializable class. 126 */ 127 private static final long serialVersionUID = -8945609557086398241L; 128 129 130 131 // A count of the total number of entries examined. 132 @NotNull private final AtomicLong entriesExamined; 133 134 // A count of the number of entries missing an attribute value contained in 135 // the RDN. 136 @NotNull private final AtomicLong entriesMissingRDNValues; 137 138 // A count of the total number of invalid entries encountered. 139 @NotNull private final AtomicLong invalidEntries; 140 141 // A count of the number of entries with DNs that could not be parsed. 142 @NotNull private final AtomicLong malformedDNs; 143 144 // A count of the number of entries missing a superior object class. 145 @NotNull private final AtomicLong missingSuperiorClasses; 146 147 // A count of the number of entries containing multiple structural object 148 // classes. 149 @NotNull private final AtomicLong multipleStructuralClasses; 150 151 // A count of the number of entries with RDNs that violate the associated 152 // name form. 153 @NotNull private final AtomicLong nameFormViolations; 154 155 // A count of the number of entries without any object class. 156 @NotNull private final AtomicLong noObjectClasses; 157 158 // A count of the number of entries without a structural object class. 159 @NotNull private final AtomicLong noStructuralClass; 160 161 // Indicates whether an entry should be considered invalid if it contains an 162 // attribute value which violates the associated attribute syntax. 163 private boolean checkAttributeSyntax; 164 165 // Indicates whether an entry should be considered invalid if it contains one 166 // or more attribute values in its RDN that are not present in the set of 167 // entry attributes. 168 private boolean checkEntryMissingRDNValues; 169 170 // Indicates whether an entry should be considered invalid if its DN cannot be 171 // parsed. 172 private boolean checkMalformedDNs; 173 174 // Indicates whether an entry should be considered invalid if it is missing 175 // attributes required by its object classes or DIT content rule. 176 private boolean checkMissingAttributes; 177 178 // Indicates whether an entry should be considered invalid if it is missing 179 // one or more superior object classes. 180 private boolean checkMissingSuperiorObjectClasses; 181 182 // Indicates whether an entry should be considered invalid if its RDN does not 183 // conform to name form requirements. 184 private boolean checkNameForms; 185 186 // Indicates whether an entry should be considered invalid if it contains any 187 // attributes which are not allowed by its object classes or DIT content rule. 188 private boolean checkProhibitedAttributes; 189 190 // Indicates whether an entry should be considered invalid if it contains an 191 // auxiliary class that is not allowed by its DIT content rule or an abstract 192 // class that is not associated with a non-abstract class. 193 private boolean checkProhibitedObjectClasses; 194 195 // Indicates whether an entry should be considered invalid if it contains any 196 // attribute defined as single-valued with more than one values. 197 private boolean checkSingleValuedAttributes; 198 199 // Indicates whether an entry should be considered invalid if it does not 200 // contain exactly one structural object class. 201 private boolean checkStructuralObjectClasses; 202 203 // Indicates whether an entry should be considered invalid if it contains an 204 // attribute which is not defined in the schema. 205 private boolean checkUndefinedAttributes; 206 207 // Indicates whether an entry should be considered invalid if it contains an 208 // object class which is not defined in the schema. 209 private boolean checkUndefinedObjectClasses; 210 211 // A map of the attributes with values violating the associated syntax to the 212 // number of values found violating the syntax. 213 @NotNull private final ConcurrentHashMap<String,AtomicLong> 214 attributesViolatingSyntax; 215 216 // A map of the required attribute types that were missing from entries to 217 // the number of entries missing them. 218 @NotNull private final ConcurrentHashMap<String,AtomicLong> missingAttributes; 219 220 // A map of the prohibited attribute types that were included in entries to 221 // the number of entries referencing them. 222 @NotNull private final ConcurrentHashMap<String,AtomicLong> 223 prohibitedAttributes; 224 225 // A map of the prohibited auxiliary object classes that were included in 226 // entries to the number of entries referencing them. 227 @NotNull private final ConcurrentHashMap<String,AtomicLong> 228 prohibitedObjectClasses; 229 230 // A map of the single-valued attributes with multiple values to the number 231 // of entries with multiple values for those attributes. 232 @NotNull private final ConcurrentHashMap<String,AtomicLong> 233 singleValueViolations; 234 235 // A map of undefined attribute types to the number of entries referencing 236 // them. 237 @NotNull private final ConcurrentHashMap<String,AtomicLong> 238 undefinedAttributes; 239 240 // A map of undefined object classes to the number of entries referencing 241 // them. 242 @NotNull private final ConcurrentHashMap<String,AtomicLong> 243 undefinedObjectClasses; 244 245 // The schema against which entries will be validated. 246 @NotNull private final Schema schema; 247 248 // The attribute types for which to ignore syntax violations. 249 @NotNull private Set<AttributeTypeDefinition> ignoreSyntaxViolationTypes; 250 251 252 253 /** 254 * Creates a new entry validator that will validate entries according to the 255 * provided schema. 256 * 257 * @param schema The schema against which entries will be validated. 258 */ 259 public EntryValidator(@NotNull final Schema schema) 260 { 261 this.schema = schema; 262 263 checkAttributeSyntax = true; 264 checkEntryMissingRDNValues = true; 265 checkMalformedDNs = true; 266 checkMissingAttributes = true; 267 checkMissingSuperiorObjectClasses = true; 268 checkNameForms = true; 269 checkProhibitedAttributes = true; 270 checkProhibitedObjectClasses = true; 271 checkSingleValuedAttributes = true; 272 checkStructuralObjectClasses = true; 273 checkUndefinedAttributes = true; 274 checkUndefinedObjectClasses = true; 275 276 ignoreSyntaxViolationTypes = Collections.emptySet(); 277 278 entriesExamined = new AtomicLong(0L); 279 entriesMissingRDNValues = new AtomicLong(0L); 280 invalidEntries = new AtomicLong(0L); 281 malformedDNs = new AtomicLong(0L); 282 missingSuperiorClasses = new AtomicLong(0L); 283 multipleStructuralClasses = new AtomicLong(0L); 284 nameFormViolations = new AtomicLong(0L); 285 noObjectClasses = new AtomicLong(0L); 286 noStructuralClass = new AtomicLong(0L); 287 288 attributesViolatingSyntax = 289 new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 290 missingAttributes = 291 new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 292 prohibitedAttributes = 293 new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 294 prohibitedObjectClasses = 295 new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 296 singleValueViolations = 297 new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 298 undefinedAttributes = 299 new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 300 undefinedObjectClasses = 301 new ConcurrentHashMap<>(StaticUtils.computeMapCapacity(20)); 302 } 303 304 305 306 /** 307 * Indicates whether the entry validator should consider entries invalid if 308 * they are missing attributes which are required by the object classes or 309 * DIT content rule (if applicable) for the entry. 310 * 311 * @return {@code true} if entries that are missing attributes required by 312 * its object classes or DIT content rule should be considered 313 * invalid, or {@code false} if not. 314 */ 315 public boolean checkMissingAttributes() 316 { 317 return checkMissingAttributes; 318 } 319 320 321 322 /** 323 * Specifies whether the entry validator should consider entries invalid if 324 * they are missing attributes which are required by the object classes or DIT 325 * content rule (if applicable) for the entry. 326 * 327 * @param checkMissingAttributes Indicates whether the entry validator 328 * should consider entries invalid if they are 329 * missing required attributes. 330 */ 331 public void setCheckMissingAttributes(final boolean checkMissingAttributes) 332 { 333 this.checkMissingAttributes = checkMissingAttributes; 334 } 335 336 337 338 /** 339 * Indicates whether the entry validator should consider entries invalid if 340 * they are missing any superior classes for the included set of object 341 * classes. 342 * 343 * @return {@code true} if entries that are missing superior classes should 344 * be considered invalid, or {@code false} if not. 345 */ 346 public boolean checkMissingSuperiorObjectClasses() 347 { 348 return checkMissingSuperiorObjectClasses; 349 } 350 351 352 353 /** 354 * Specifies whether the entry validator should consider entries invalid if 355 * they are missing any superior classes for the included set of object 356 * classes. 357 * 358 * @param checkMissingSuperiorObjectClasses Indicates whether the entry 359 * validator should consider 360 * entries invalid if they are 361 * missing any superior classes for 362 * the included set of object 363 * classes. 364 */ 365 public void setCheckMissingSuperiorObjectClasses( 366 final boolean checkMissingSuperiorObjectClasses) 367 { 368 this.checkMissingSuperiorObjectClasses = checkMissingSuperiorObjectClasses; 369 } 370 371 372 373 /** 374 * Indicates whether the entry validator should consider entries invalid if 375 * their DNs cannot be parsed. 376 * 377 * @return {@code true} if entries with malformed DNs should be considered 378 * invalid, or {@code false} if not. 379 */ 380 public boolean checkMalformedDNs() 381 { 382 return checkMalformedDNs; 383 } 384 385 386 387 /** 388 * Specifies whether the entry validator should consider entries invalid if 389 * their DNs cannot be parsed. 390 * 391 * @param checkMalformedDNs Specifies whether entries with malformed DNs 392 * should be considered invalid. 393 */ 394 public void setCheckMalformedDNs(final boolean checkMalformedDNs) 395 { 396 this.checkMalformedDNs = checkMalformedDNs; 397 } 398 399 400 401 /** 402 * Indicates whether the entry validator should consider entries invalid if 403 * they contain one or more attribute values in their RDN that are not present 404 * in the set of entry attributes. 405 * 406 * @return {@code true} if entries missing one or more attribute values 407 * included in their RDNs should be considered invalid, or 408 * {@code false} if not. 409 */ 410 public boolean checkEntryMissingRDNValues() 411 { 412 return checkEntryMissingRDNValues; 413 } 414 415 416 417 /** 418 * Specifies whether the entry validator should consider entries invalid if 419 * they contain one or more attribute values in their RDN that are not present 420 * in the set of entry attributes. 421 * 422 * @param checkEntryMissingRDNValues Indicates whether the entry validator 423 * should consider entries invalid if they 424 * contain one or more attribute values in 425 * their RDN that are not present in the 426 * set of entry attributes. 427 */ 428 public void setCheckEntryMissingRDNValues( 429 final boolean checkEntryMissingRDNValues) 430 { 431 this.checkEntryMissingRDNValues = checkEntryMissingRDNValues; 432 } 433 434 435 436 /** 437 * Indicates whether the entry validator should consider entries invalid if 438 * the attributes contained in the RDN violate the constraints of the 439 * associated name form. 440 * 441 * @return {@code true} if entries with RDNs that do not conform to the 442 * associated name form should be considered invalid, or 443 * {@code false} if not. 444 */ 445 public boolean checkNameForms() 446 { 447 return checkNameForms; 448 } 449 450 451 452 /** 453 * Specifies whether the entry validator should consider entries invalid if 454 * the attributes contained in the RDN violate the constraints of the 455 * associated name form. 456 * 457 * @param checkNameForms Indicates whether the entry validator should 458 * consider entries invalid if their RDNs violate name 459 * form constraints. 460 */ 461 public void setCheckNameForms(final boolean checkNameForms) 462 { 463 this.checkNameForms = checkNameForms; 464 } 465 466 467 468 /** 469 * Indicates whether the entry validator should consider entries invalid if 470 * they contain attributes which are not allowed by (or are prohibited by) the 471 * object classes and DIT content rule (if applicable) for the entry. 472 * 473 * @return {@code true} if entries should be considered invalid if they 474 * contain attributes which are not allowed, or {@code false} if not. 475 */ 476 public boolean checkProhibitedAttributes() 477 { 478 return checkProhibitedAttributes; 479 } 480 481 482 483 /** 484 * Specifies whether the entry validator should consider entries invalid if 485 * they contain attributes which are not allowed by (or are prohibited by) the 486 * object classes and DIT content rule (if applicable) for the entry. 487 * 488 * @param checkProhibitedAttributes Indicates whether entries should be 489 * considered invalid if they contain 490 * attributes which are not allowed. 491 */ 492 public void setCheckProhibitedAttributes( 493 final boolean checkProhibitedAttributes) 494 { 495 this.checkProhibitedAttributes = checkProhibitedAttributes; 496 } 497 498 499 500 /** 501 * Indicates whether the entry validator should consider entries invalid if 502 * they contain auxiliary object classes which are not allowed by the DIT 503 * content rule (if applicable) for the entry, or if they contain any abstract 504 * object classes which are not subclassed by any non-abstract classes 505 * included in the entry. 506 * 507 * @return {@code true} if entries should be considered invalid if they 508 * contain prohibited object classes, or {@code false} if not. 509 */ 510 public boolean checkProhibitedObjectClasses() 511 { 512 return checkProhibitedObjectClasses; 513 } 514 515 516 517 /** 518 * Specifies whether the entry validator should consider entries invalid if 519 * they contain auxiliary object classes which are not allowed by the DIT 520 * content rule (if applicable) for the entry, or if they contain any abstract 521 * object classes which are not subclassed by any non-abstract classes 522 * included in the entry. 523 * 524 * @param checkProhibitedObjectClasses Indicates whether entries should be 525 * considered invalid if they contain 526 * prohibited object classes. 527 */ 528 public void setCheckProhibitedObjectClasses( 529 final boolean checkProhibitedObjectClasses) 530 { 531 this.checkProhibitedObjectClasses = checkProhibitedObjectClasses; 532 } 533 534 535 536 /** 537 * Indicates whether the entry validator should consider entries invalid if 538 * they they contain attributes with more than one value which are declared as 539 * single-valued in the schema. 540 * 541 * @return {@code true} if entries should be considered invalid if they 542 * contain single-valued attributes with more than one value, or 543 * {@code false} if not. 544 */ 545 public boolean checkSingleValuedAttributes() 546 { 547 return checkSingleValuedAttributes; 548 } 549 550 551 552 /** 553 * Specifies whether the entry validator should consider entries invalid if 554 * they contain attributes with more than one value which are declared as 555 * single-valued in the schema. 556 * 557 * @param checkSingleValuedAttributes Indicates whether entries should be 558 * considered invalid if they contain 559 * single-valued attributes with more 560 * than one value. 561 */ 562 public void setCheckSingleValuedAttributes( 563 final boolean checkSingleValuedAttributes) 564 { 565 this.checkSingleValuedAttributes = checkSingleValuedAttributes; 566 } 567 568 569 570 /** 571 * Indicates whether the entry validator should consider entries invalid if 572 * they do not contain exactly one structural object class (i.e., either do 573 * not have any structural object class, or have more than one). 574 * 575 * @return {@code true} if entries should be considered invalid if they do 576 * not have exactly one structural object class, or {@code false} if 577 * not. 578 */ 579 public boolean checkStructuralObjectClasses() 580 { 581 return checkStructuralObjectClasses; 582 } 583 584 585 586 /** 587 * Specifies whether the entry validator should consider entries invalid if 588 * they do not contain exactly one structural object class (i.e., either do 589 * not have any structural object class, or have more than one). 590 * 591 * @param checkStructuralObjectClasses Indicates whether entries should be 592 * considered invalid if they do not 593 * have exactly one structural object 594 * class. 595 */ 596 public void setCheckStructuralObjectClasses( 597 final boolean checkStructuralObjectClasses) 598 { 599 this.checkStructuralObjectClasses = checkStructuralObjectClasses; 600 } 601 602 603 604 /** 605 * Indicates whether the entry validator should consider entries invalid if 606 * they contain attributes which violate the associated attribute syntax. 607 * 608 * @return {@code true} if entries should be considered invalid if they 609 * contain attribute values which violate the associated attribute 610 * syntax, or {@code false} if not. 611 */ 612 public boolean checkAttributeSyntax() 613 { 614 return checkAttributeSyntax; 615 } 616 617 618 619 /** 620 * Specifies whether the entry validator should consider entries invalid if 621 * they contain attributes which violate the associated attribute syntax. 622 * 623 * @param checkAttributeSyntax Indicates whether entries should be 624 * considered invalid if they violate the 625 * associated attribute syntax. 626 */ 627 public void setCheckAttributeSyntax(final boolean checkAttributeSyntax) 628 { 629 this.checkAttributeSyntax = checkAttributeSyntax; 630 } 631 632 633 634 /** 635 * Retrieves the set of attribute types for which syntax violations should be 636 * ignored. If {@link #checkAttributeSyntax()} returns {@code true}, then 637 * any attribute syntax violations will be flagged for all attributes except 638 * those attributes in this set. If {@code checkAttributeSyntax()} returns 639 * {@code false}, then all syntax violations will be ignored. 640 * 641 * @return The set of attribute types for which syntax violations should be 642 * ignored. 643 */ 644 @NotNull() 645 public Set<AttributeTypeDefinition> getIgnoreSyntaxViolationsAttributeTypes() 646 { 647 return ignoreSyntaxViolationTypes; 648 } 649 650 651 652 /** 653 * Specifies the set of attribute types for which syntax violations should be 654 * ignored. This method will only have any effect if 655 * {@link #checkAttributeSyntax()} returns {@code true}. 656 * 657 * @param attributeTypes The definitions for the attribute types for which 658 * to ignore syntax violations. It may be 659 * {@code null} or empty if no violations should be 660 * ignored. 661 */ 662 public void setIgnoreSyntaxViolationAttributeTypes( 663 @Nullable final AttributeTypeDefinition... attributeTypes) 664 { 665 if (attributeTypes == null) 666 { 667 ignoreSyntaxViolationTypes = Collections.emptySet(); 668 } 669 else 670 { 671 ignoreSyntaxViolationTypes = Collections.unmodifiableSet( 672 new HashSet<>(StaticUtils.toList(attributeTypes))); 673 } 674 } 675 676 677 678 /** 679 * Specifies the names or OIDs of the attribute types for which syntax 680 * violations should be ignored. This method will only have any effect if 681 * {@link #checkAttributeSyntax()} returns {@code true}. 682 * 683 * @param attributeTypes The names or OIDs of the attribute types for which 684 * to ignore syntax violations. It may be 685 * {@code null} or empty if no violations should be 686 * ignored. 687 */ 688 public void setIgnoreSyntaxViolationAttributeTypes( 689 @Nullable final String... attributeTypes) 690 { 691 setIgnoreSyntaxViolationAttributeTypes(StaticUtils.toList(attributeTypes)); 692 } 693 694 695 696 /** 697 * Specifies the names or OIDs of the attribute types for which syntax 698 * violations should be ignored. This method will only have any effect if 699 * {@link #checkAttributeSyntax()} returns {@code true}. 700 * 701 * @param attributeTypes The names or OIDs of the attribute types for which 702 * to ignore syntax violations. It may be 703 * {@code null} or empty if no violations should be 704 * ignored. Any attribute types not defined in the 705 * schema will be ignored. 706 */ 707 public void setIgnoreSyntaxViolationAttributeTypes( 708 @Nullable final Collection<String> attributeTypes) 709 { 710 if (attributeTypes == null) 711 { 712 ignoreSyntaxViolationTypes = Collections.emptySet(); 713 return; 714 } 715 716 final HashSet<AttributeTypeDefinition> atSet = 717 new HashSet<>(StaticUtils.computeMapCapacity(attributeTypes.size())); 718 for (final String s : attributeTypes) 719 { 720 final AttributeTypeDefinition d = schema.getAttributeType(s); 721 if (d != null) 722 { 723 atSet.add(d); 724 } 725 } 726 727 ignoreSyntaxViolationTypes = Collections.unmodifiableSet(atSet); 728 } 729 730 731 732 /** 733 * Indicates whether the entry validator should consider entries invalid if 734 * they contain attributes which are not defined in the schema. 735 * 736 * @return {@code true} if entries should be considered invalid if they 737 * contain attributes which are not defined in the schema, or 738 * {@code false} if not. 739 */ 740 public boolean checkUndefinedAttributes() 741 { 742 return checkUndefinedAttributes; 743 } 744 745 746 747 /** 748 * Specifies whether the entry validator should consider entries invalid if 749 * they contain attributes which are not defined in the schema. 750 * 751 * @param checkUndefinedAttributes Indicates whether entries should be 752 * considered invalid if they contain 753 * attributes which are not defined in the 754 * schema, or {@code false} if not. 755 */ 756 public void setCheckUndefinedAttributes( 757 final boolean checkUndefinedAttributes) 758 { 759 this.checkUndefinedAttributes = checkUndefinedAttributes; 760 } 761 762 763 764 /** 765 * Indicates whether the entry validator should consider entries invalid if 766 * they contain object classes which are not defined in the schema. 767 * 768 * @return {@code true} if entries should be considered invalid if they 769 * contain object classes which are not defined in the schema, or 770 * {@code false} if not. 771 */ 772 public boolean checkUndefinedObjectClasses() 773 { 774 return checkUndefinedObjectClasses; 775 } 776 777 778 779 /** 780 * Specifies whether the entry validator should consider entries invalid if 781 * they contain object classes which are not defined in the schema. 782 * 783 * @param checkUndefinedObjectClasses Indicates whether entries should be 784 * considered invalid if they contain 785 * object classes which are not defined 786 * in the schema. 787 */ 788 public void setCheckUndefinedObjectClasses( 789 final boolean checkUndefinedObjectClasses) 790 { 791 this.checkUndefinedObjectClasses = checkUndefinedObjectClasses; 792 } 793 794 795 796 /** 797 * Indicates whether the provided entry passes all of the enabled types of 798 * validation. 799 * 800 * @param entry The entry to be examined. It must not be 801 * {@code null}. 802 * @param invalidReasons A list to which messages may be added which provide 803 * information about why the entry is invalid. It may 804 * be {@code null} if this information is not needed. 805 * 806 * @return {@code true} if the entry conforms to all of the enabled forms of 807 * validation, or {@code false} if the entry fails at least one of 808 * the tests. 809 */ 810 public boolean entryIsValid(@NotNull final Entry entry, 811 @Nullable final List<String> invalidReasons) 812 { 813 Validator.ensureNotNull(entry); 814 815 boolean entryValid = true; 816 entriesExamined.incrementAndGet(); 817 818 // Get the parsed DN for the entry. 819 RDN rdn = null; 820 try 821 { 822 rdn = entry.getParsedDN().getRDN(); 823 } 824 catch (final LDAPException le) 825 { 826 Debug.debugException(le); 827 if (checkMalformedDNs) 828 { 829 entryValid = false; 830 malformedDNs.incrementAndGet(); 831 if (invalidReasons != null) 832 { 833 invalidReasons.add(ERR_ENTRY_MALFORMED_DN.get( 834 StaticUtils.getExceptionMessage(le))); 835 } 836 } 837 } 838 839 // Get the object class descriptions for the object classes in the entry. 840 final HashSet<ObjectClassDefinition> ocSet = 841 new HashSet<>(StaticUtils.computeMapCapacity(10)); 842 final boolean missingOC = 843 (! getObjectClasses(entry, ocSet, invalidReasons)); 844 if (missingOC) 845 { 846 entryValid = false; 847 } 848 849 // If the entry was not missing any object classes, then get the structural 850 // class for the entry and use it to get the associated DIT content rule and 851 // name form. 852 DITContentRuleDefinition ditContentRule = null; 853 NameFormDefinition nameForm = null; 854 if (! missingOC) 855 { 856 final AtomicReference<ObjectClassDefinition> ref = 857 new AtomicReference<>(null); 858 entryValid &= getStructuralClass(ocSet, ref, invalidReasons); 859 final ObjectClassDefinition structuralClass = ref.get(); 860 if (structuralClass != null) 861 { 862 ditContentRule = schema.getDITContentRule(structuralClass.getOID()); 863 nameForm = 864 schema.getNameFormByObjectClass(structuralClass.getNameOrOID()); 865 } 866 } 867 868 // If we should check for missing required attributes, then do so. 869 Set<AttributeTypeDefinition> requiredAttrs = Collections.emptySet(); 870 if (checkMissingAttributes || checkProhibitedAttributes) 871 { 872 requiredAttrs = getRequiredAttributes(ocSet, ditContentRule); 873 if (checkMissingAttributes) 874 { 875 entryValid &= checkForMissingAttributes(entry, rdn, requiredAttrs, 876 invalidReasons); 877 } 878 } 879 880 // Iterate through all of the attributes in the entry. Make sure that they 881 // are all defined in the schema, that they are allowed to be present in the 882 // entry, that their values conform to the associated syntax, and that any 883 // single-valued attributes have only one value. 884 Set<AttributeTypeDefinition> optionalAttrs = Collections.emptySet(); 885 if (checkProhibitedAttributes) 886 { 887 optionalAttrs = 888 getOptionalAttributes(ocSet, ditContentRule, requiredAttrs); 889 } 890 for (final Attribute a : entry.getAttributes()) 891 { 892 entryValid &= 893 checkAttribute(a, requiredAttrs, optionalAttrs, invalidReasons); 894 } 895 896 // If there is a DIT content rule, then check to ensure that all of the 897 // auxiliary object classes are allowed. 898 if (checkProhibitedObjectClasses && (ditContentRule != null)) 899 { 900 entryValid &= 901 checkAuxiliaryClasses(ocSet, ditContentRule, invalidReasons); 902 } 903 904 // Check the entry's RDN to ensure that all attributes are defined in the 905 // schema, allowed to be present, and comply with the name form. 906 if (rdn != null) 907 { 908 entryValid &= checkRDN(rdn, entry, requiredAttrs, optionalAttrs, nameForm, 909 invalidReasons); 910 } 911 912 if (! entryValid) 913 { 914 invalidEntries.incrementAndGet(); 915 } 916 917 return entryValid; 918 } 919 920 921 922 /** 923 * Gets the object classes for the entry, including any that weren't 924 * explicitly included but should be because they were superior to classes 925 * that were included. 926 * 927 * @param entry The entry to examine. 928 * @param ocSet The set into which the object class definitions 929 * should be placed. 930 * @param invalidReasons A list to which messages may be added which provide 931 * information about why the entry is invalid. It may 932 * be {@code null} if this information is not needed. 933 * 934 * @return {@code true} if the entry passed all validation processing 935 * performed by this method, or {@code false} if there were any 936 * failures. 937 */ 938 private boolean getObjectClasses(@NotNull final Entry entry, 939 @NotNull final HashSet<ObjectClassDefinition> ocSet, 940 @Nullable final List<String> invalidReasons) 941 { 942 final String[] ocValues = entry.getObjectClassValues(); 943 if ((ocValues == null) || (ocValues.length == 0)) 944 { 945 noObjectClasses.incrementAndGet(); 946 if (invalidReasons != null) 947 { 948 invalidReasons.add(ERR_ENTRY_NO_OCS.get()); 949 } 950 return false; 951 } 952 953 boolean entryValid = true; 954 final HashSet<String> missingOCs = 955 new HashSet<>(StaticUtils.computeMapCapacity(ocValues.length)); 956 for (final String ocName : entry.getObjectClassValues()) 957 { 958 final ObjectClassDefinition d = schema.getObjectClass(ocName); 959 if (d == null) 960 { 961 if (checkUndefinedObjectClasses) 962 { 963 entryValid = false; 964 missingOCs.add(StaticUtils.toLowerCase(ocName)); 965 updateCount(ocName, undefinedObjectClasses); 966 if (invalidReasons != null) 967 { 968 invalidReasons.add(ERR_ENTRY_UNDEFINED_OC.get(ocName)); 969 } 970 } 971 } 972 else 973 { 974 ocSet.add(d); 975 } 976 } 977 978 for (final ObjectClassDefinition d : new HashSet<>(ocSet)) 979 { 980 entryValid &= addSuperiorClasses(d, ocSet, missingOCs, invalidReasons); 981 } 982 983 return entryValid; 984 } 985 986 987 988 /** 989 * Recursively adds the definition superior class for the provided object 990 * class definition to the provided set, if it is not already present. 991 * 992 * @param d The object class definition to process. 993 * @param ocSet The set into which the object class definitions 994 * should be placed. 995 * @param missingOCNames The names of the object classes we already know are 996 * missing and therefore shouldn't be flagged again. 997 * @param invalidReasons A list to which messages may be added which provide 998 * information about why the entry is invalid. It may 999 * be {@code null} if this information is not needed. 1000 * 1001 * @return {@code true} if the entry passed all validation processing 1002 * performed by this method, or {@code false} if there were any 1003 * failures. 1004 */ 1005 private boolean addSuperiorClasses(@NotNull final ObjectClassDefinition d, 1006 @NotNull final HashSet<ObjectClassDefinition> ocSet, 1007 @NotNull final HashSet<String> missingOCNames, 1008 @Nullable final List<String> invalidReasons) 1009 { 1010 boolean entryValid = true; 1011 1012 for (final String ocName : d.getSuperiorClasses()) 1013 { 1014 final ObjectClassDefinition supOC = schema.getObjectClass(ocName); 1015 if (supOC == null) 1016 { 1017 if (checkUndefinedObjectClasses) 1018 { 1019 entryValid = false; 1020 final String lowerName = StaticUtils.toLowerCase(ocName); 1021 if (! missingOCNames.contains(lowerName)) 1022 { 1023 missingOCNames.add(lowerName); 1024 updateCount(ocName, undefinedObjectClasses); 1025 if (invalidReasons != null) 1026 { 1027 invalidReasons.add(ERR_ENTRY_UNDEFINED_SUP_OC.get( 1028 d.getNameOrOID(), ocName)); 1029 } 1030 } 1031 } 1032 } 1033 else 1034 { 1035 if (! ocSet.contains(supOC)) 1036 { 1037 ocSet.add(supOC); 1038 if (checkMissingSuperiorObjectClasses) 1039 { 1040 entryValid = false; 1041 missingSuperiorClasses.incrementAndGet(); 1042 if (invalidReasons != null) 1043 { 1044 invalidReasons.add(ERR_ENTRY_MISSING_SUP_OC.get( 1045 supOC.getNameOrOID(), d.getNameOrOID())); 1046 } 1047 } 1048 } 1049 1050 entryValid &= 1051 addSuperiorClasses(supOC, ocSet, missingOCNames, invalidReasons); 1052 } 1053 } 1054 1055 return entryValid; 1056 } 1057 1058 1059 1060 /** 1061 * Retrieves the structural object class from the set of provided object 1062 * classes. 1063 * 1064 * @param ocSet The set of object class definitions for the entry. 1065 * @param structuralClass The reference that will be updated with the 1066 * entry's structural object class. 1067 * @param invalidReasons A list to which messages may be added which 1068 * provide provide information about why the entry is 1069 * invalid. It may be {@code null} if this 1070 * information is not needed. 1071 * 1072 * @return {@code true} if the entry passes all validation checks performed 1073 * by this method, or {@code false} if not. 1074 */ 1075 private boolean getStructuralClass( 1076 @NotNull final HashSet<ObjectClassDefinition> ocSet, 1077 @NotNull final AtomicReference<ObjectClassDefinition> structuralClass, 1078 @Nullable final List<String> invalidReasons) 1079 { 1080 final HashSet<ObjectClassDefinition> ocCopy = new HashSet<>(ocSet); 1081 for (final ObjectClassDefinition d : ocSet) 1082 { 1083 final ObjectClassType t = d.getObjectClassType(schema); 1084 if (t == ObjectClassType.STRUCTURAL) 1085 { 1086 ocCopy.removeAll(d.getSuperiorClasses(schema, true)); 1087 } 1088 else if (t == ObjectClassType.AUXILIARY) 1089 { 1090 ocCopy.remove(d); 1091 ocCopy.removeAll(d.getSuperiorClasses(schema, true)); 1092 } 1093 } 1094 1095 // Iterate through the set of remaining classes and strip out any 1096 // abstract classes. 1097 boolean entryValid = true; 1098 Iterator<ObjectClassDefinition> iterator = ocCopy.iterator(); 1099 while (iterator.hasNext()) 1100 { 1101 final ObjectClassDefinition d = iterator.next(); 1102 if (d.getObjectClassType(schema) == ObjectClassType.ABSTRACT) 1103 { 1104 if (checkProhibitedObjectClasses) 1105 { 1106 entryValid = false; 1107 updateCount(d.getNameOrOID(), prohibitedObjectClasses); 1108 if (invalidReasons != null) 1109 { 1110 invalidReasons.add(ERR_ENTRY_INVALID_ABSTRACT_CLASS.get( 1111 d.getNameOrOID())); 1112 } 1113 } 1114 iterator.remove(); 1115 } 1116 } 1117 1118 switch (ocCopy.size()) 1119 { 1120 case 0: 1121 if (checkStructuralObjectClasses) 1122 { 1123 entryValid = false; 1124 noStructuralClass.incrementAndGet(); 1125 if (invalidReasons != null) 1126 { 1127 invalidReasons.add(ERR_ENTRY_NO_STRUCTURAL_CLASS.get()); 1128 } 1129 } 1130 break; 1131 1132 case 1: 1133 structuralClass.set(ocCopy.iterator().next()); 1134 break; 1135 1136 default: 1137 if (checkStructuralObjectClasses) 1138 { 1139 entryValid = false; 1140 multipleStructuralClasses.incrementAndGet(); 1141 if (invalidReasons != null) 1142 { 1143 final StringBuilder ocList = new StringBuilder(); 1144 iterator = ocCopy.iterator(); 1145 while (iterator.hasNext()) 1146 { 1147 ocList.append(iterator.next().getNameOrOID()); 1148 if (iterator.hasNext()) 1149 { 1150 ocList.append(", "); 1151 } 1152 } 1153 invalidReasons.add( 1154 ERR_ENTRY_MULTIPLE_STRUCTURAL_CLASSES.get(ocList)); 1155 } 1156 } 1157 break; 1158 } 1159 1160 return entryValid; 1161 } 1162 1163 1164 1165 /** 1166 * Retrieves the set of attributes which must be present in entries with the 1167 * provided set of object classes and DIT content rule. 1168 * 1169 * @param ocSet The set of object classes for the entry. 1170 * @param ditContentRule The DIT content rule for the entry, if defined. 1171 * 1172 * @return The set of attributes which must be present in entries with the 1173 * provided set of object classes and DIT content rule. 1174 */ 1175 @NotNull() 1176 private HashSet<AttributeTypeDefinition> getRequiredAttributes( 1177 @NotNull final HashSet<ObjectClassDefinition> ocSet, 1178 @Nullable final DITContentRuleDefinition ditContentRule) 1179 { 1180 final HashSet<AttributeTypeDefinition> attrSet = 1181 new HashSet<>(StaticUtils.computeMapCapacity(20)); 1182 for (final ObjectClassDefinition oc : ocSet) 1183 { 1184 attrSet.addAll(oc.getRequiredAttributes(schema, false)); 1185 } 1186 1187 if (ditContentRule != null) 1188 { 1189 for (final String s : ditContentRule.getRequiredAttributes()) 1190 { 1191 final AttributeTypeDefinition d = schema.getAttributeType(s); 1192 if (d != null) 1193 { 1194 attrSet.add(d); 1195 } 1196 } 1197 } 1198 1199 return attrSet; 1200 } 1201 1202 1203 1204 /** 1205 * Retrieves the set of attributes which may optionally be present in entries 1206 * with the provided set of object classes and DIT content rule. 1207 * 1208 * @param ocSet The set of object classes for the entry. 1209 * @param ditContentRule The DIT content rule for the entry, if defined. 1210 * @param requiredAttrSet The set of required attributes for the entry. 1211 * 1212 * @return The set of attributes which may optionally be present in entries 1213 * with the provided set of object classes and DIT content rule. 1214 */ 1215 @NotNull() 1216 private HashSet<AttributeTypeDefinition> getOptionalAttributes( 1217 @NotNull final HashSet<ObjectClassDefinition> ocSet, 1218 @Nullable final DITContentRuleDefinition ditContentRule, 1219 @NotNull final Set<AttributeTypeDefinition> requiredAttrSet) 1220 { 1221 final HashSet<AttributeTypeDefinition> attrSet = 1222 new HashSet<>(StaticUtils.computeMapCapacity(20)); 1223 for (final ObjectClassDefinition oc : ocSet) 1224 { 1225 if (oc.hasNameOrOID("extensibleObject") || 1226 oc.hasNameOrOID("1.3.6.1.4.1.1466.101.120.111")) 1227 { 1228 attrSet.addAll(schema.getUserAttributeTypes()); 1229 break; 1230 } 1231 1232 for (final AttributeTypeDefinition d : 1233 oc.getOptionalAttributes(schema, false)) 1234 { 1235 if (! requiredAttrSet.contains(d)) 1236 { 1237 attrSet.add(d); 1238 } 1239 } 1240 } 1241 1242 if (ditContentRule != null) 1243 { 1244 for (final String s : ditContentRule.getOptionalAttributes()) 1245 { 1246 final AttributeTypeDefinition d = schema.getAttributeType(s); 1247 if ((d != null) && (! requiredAttrSet.contains(d))) 1248 { 1249 attrSet.add(d); 1250 } 1251 } 1252 1253 for (final String s : ditContentRule.getProhibitedAttributes()) 1254 { 1255 final AttributeTypeDefinition d = schema.getAttributeType(s); 1256 if (d != null) 1257 { 1258 attrSet.remove(d); 1259 } 1260 } 1261 } 1262 1263 return attrSet; 1264 } 1265 1266 1267 1268 /** 1269 * Checks the provided entry to determine whether it is missing any required 1270 * attributes. 1271 * 1272 * @param entry The entry to examine. 1273 * @param rdn The RDN for the entry, if available. 1274 * @param requiredAttrs The set of attribute types which are required to be 1275 * included in the entry. 1276 * @param invalidReasons A list to which messages may be added which provide 1277 * information about why the entry is invalid. It may 1278 * be {@code null} if this information is not needed. 1279 * 1280 * @return {@code true} if the entry has all required attributes, or 1281 * {@code false} if not. 1282 */ 1283 private boolean checkForMissingAttributes(@NotNull final Entry entry, 1284 @Nullable final RDN rdn, 1285 @NotNull final Set<AttributeTypeDefinition> requiredAttrs, 1286 @Nullable final List<String> invalidReasons) 1287 { 1288 boolean entryValid = true; 1289 1290 for (final AttributeTypeDefinition d : requiredAttrs) 1291 { 1292 boolean found = false; 1293 for (final String s : d.getNames()) 1294 { 1295 if (entry.hasAttribute(s) || ((rdn != null) && rdn.hasAttribute(s))) 1296 { 1297 found = true; 1298 break; 1299 } 1300 } 1301 1302 if (! found) 1303 { 1304 if (! (entry.hasAttribute(d.getOID()) || 1305 ((rdn != null) && (rdn.hasAttribute(d.getOID()))))) 1306 { 1307 entryValid = false; 1308 updateCount(d.getNameOrOID(), missingAttributes); 1309 if (invalidReasons != null) 1310 { 1311 invalidReasons.add(ERR_ENTRY_MISSING_REQUIRED_ATTR.get( 1312 d.getNameOrOID())); 1313 } 1314 } 1315 } 1316 } 1317 1318 return entryValid; 1319 } 1320 1321 1322 1323 /** 1324 * Checks the provided attribute to determine whether it appears to be valid. 1325 * 1326 * @param attr The attribute to examine. 1327 * @param requiredAttrs The set of attribute types which are required to be 1328 * included in the entry. 1329 * @param optionalAttrs The set of attribute types which may optionally be 1330 * included in the entry. 1331 * @param invalidReasons A list to which messages may be added which provide 1332 * information about why the entry is invalid. It may 1333 * be {@code null} if this information is not needed. 1334 * 1335 * @return {@code true} if the attribute passed all of the checks and appears 1336 * to be valid, or {@code false} if it failed any of the checks. 1337 */ 1338 private boolean checkAttribute(@NotNull final Attribute attr, 1339 @NotNull final Set<AttributeTypeDefinition> requiredAttrs, 1340 @NotNull final Set<AttributeTypeDefinition> optionalAttrs, 1341 @Nullable final List<String> invalidReasons) 1342 { 1343 boolean entryValid = true; 1344 1345 final AttributeTypeDefinition d = 1346 schema.getAttributeType(attr.getBaseName()); 1347 if (d == null) 1348 { 1349 if (checkUndefinedAttributes) 1350 { 1351 entryValid = false; 1352 updateCount(attr.getBaseName(), undefinedAttributes); 1353 if (invalidReasons != null) 1354 { 1355 invalidReasons.add(ERR_ENTRY_UNDEFINED_ATTR.get(attr.getBaseName())); 1356 } 1357 } 1358 1359 return entryValid; 1360 } 1361 1362 if (checkProhibitedAttributes && (! d.isOperational())) 1363 { 1364 if (! (requiredAttrs.contains(d) || optionalAttrs.contains(d))) 1365 { 1366 entryValid = false; 1367 updateCount(d.getNameOrOID(), prohibitedAttributes); 1368 if (invalidReasons != null) 1369 { 1370 invalidReasons.add(ERR_ENTRY_ATTR_NOT_ALLOWED.get(d.getNameOrOID())); 1371 } 1372 } 1373 } 1374 1375 final ASN1OctetString[] rawValues = attr.getRawValues(); 1376 if (checkSingleValuedAttributes && d.isSingleValued() && 1377 (rawValues.length > 1)) 1378 { 1379 entryValid = false; 1380 updateCount(d.getNameOrOID(), singleValueViolations); 1381 if (invalidReasons != null) 1382 { 1383 invalidReasons.add( 1384 ERR_ENTRY_ATTR_HAS_MULTIPLE_VALUES.get(d.getNameOrOID())); 1385 } 1386 } 1387 1388 if (checkAttributeSyntax) 1389 { 1390 if (! ignoreSyntaxViolationTypes.contains(d)) 1391 { 1392 final MatchingRule r = 1393 MatchingRule.selectEqualityMatchingRule(d.getNameOrOID(), schema); 1394 final Map<String, String[]> extensions = d.getExtensions(); 1395 for (final ASN1OctetString v : rawValues) 1396 { 1397 try 1398 { 1399 r.normalize(v); 1400 } 1401 catch (final LDAPException le) 1402 { 1403 Debug.debugException(le); 1404 entryValid = false; 1405 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1406 if (invalidReasons != null) 1407 { 1408 invalidReasons.add(ERR_ENTRY_ATTR_INVALID_SYNTAX.get( 1409 v.stringValue(), d.getNameOrOID(), 1410 StaticUtils.getExceptionMessage(le))); 1411 } 1412 } 1413 1414 1415 // If the attribute type definition includes an X-ALLOWED-VALUE 1416 // extension, then make sure the value is in that set. 1417 final String[] allowedValues = extensions.get("X-ALLOWED-VALUE"); 1418 if (allowedValues != null) 1419 { 1420 boolean isAllowed = false; 1421 for (final String allowedValue : allowedValues) 1422 { 1423 try 1424 { 1425 if (r.valuesMatch(v, new ASN1OctetString(allowedValue))) 1426 { 1427 isAllowed = true; 1428 break; 1429 } 1430 } 1431 catch (final Exception e) 1432 { 1433 Debug.debugException(e); 1434 } 1435 } 1436 1437 if (! isAllowed) 1438 { 1439 entryValid = false; 1440 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1441 if (invalidReasons != null) 1442 { 1443 invalidReasons.add(ERR_ENTRY_ATTR_VALUE_NOT_ALLOWED.get( 1444 v.stringValue(), d.getNameOrOID())); 1445 } 1446 } 1447 } 1448 1449 1450 // If the attribute type definition includes an X-VALUE-REGEX 1451 // extension, then make sure the value matches one of those regexes. 1452 final String[] valueRegexes = extensions.get("X-VALUE-REGEX"); 1453 if (valueRegexes != null) 1454 { 1455 boolean matchesRegex = false; 1456 for (final String regex : valueRegexes) 1457 { 1458 try 1459 { 1460 final Pattern pattern = Pattern.compile(regex); 1461 if (pattern.matcher(v.stringValue()).matches()) 1462 { 1463 matchesRegex = true; 1464 break; 1465 } 1466 } 1467 catch (final Exception e) 1468 { 1469 Debug.debugException(e); 1470 } 1471 } 1472 1473 if (! matchesRegex) 1474 { 1475 entryValid = false; 1476 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1477 if (invalidReasons != null) 1478 { 1479 invalidReasons.add( 1480 ERR_ENTRY_ATTR_VALUE_NOT_ALLOWED_BY_REGEX.get( 1481 v.stringValue(), d.getNameOrOID())); 1482 } 1483 } 1484 } 1485 1486 1487 // If the attribute type definition includes an X-MIN-VALUE-LENGTH 1488 // extension, then make sure the value is long enough. 1489 final String[] minValueLengths = extensions.get("X-MIN-VALUE-LENGTH"); 1490 if (minValueLengths != null) 1491 { 1492 int minLength = 0; 1493 for (final String s : minValueLengths) 1494 { 1495 try 1496 { 1497 minLength = Math.max(minLength, Integer.parseInt(s)); 1498 } 1499 catch (final Exception e) 1500 { 1501 Debug.debugException(e); 1502 } 1503 } 1504 1505 if (v.stringValue().length() < minLength) 1506 { 1507 entryValid = false; 1508 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1509 if (invalidReasons != null) 1510 { 1511 invalidReasons.add( 1512 ERR_ENTRY_ATTR_VALUE_SHORTER_THAN_MIN_LENGTH.get( 1513 v.stringValue(), d.getNameOrOID(), minLength)); 1514 } 1515 } 1516 } 1517 1518 1519 // If the attribute type definition includes an X-MAX-VALUE-LENGTH 1520 // extension, then make sure the value is short enough. 1521 final String[] maxValueLengths = extensions.get("X-MAX-VALUE-LENGTH"); 1522 if (maxValueLengths != null) 1523 { 1524 int maxLength = Integer.MAX_VALUE; 1525 for (final String s : maxValueLengths) 1526 { 1527 try 1528 { 1529 maxLength = Math.min(maxLength, Integer.parseInt(s)); 1530 } 1531 catch (final Exception e) 1532 { 1533 Debug.debugException(e); 1534 } 1535 } 1536 1537 if (v.stringValue().length() > maxLength) 1538 { 1539 entryValid = false; 1540 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1541 if (invalidReasons != null) 1542 { 1543 invalidReasons.add( 1544 ERR_ENTRY_ATTR_VALUE_LONGER_THAN_MAX_LENGTH.get( 1545 v.stringValue(), d.getNameOrOID(), maxLength)); 1546 } 1547 } 1548 } 1549 1550 1551 // If the attribute type definition includes an X-MIN-INT-VALUE 1552 // extension, then make sure the value is large enough. 1553 final String[] minIntValues = extensions.get("X-MIN-INT-VALUE"); 1554 if (minIntValues != null) 1555 { 1556 try 1557 { 1558 final long longValue = Long.parseLong(v.stringValue()); 1559 1560 long minAllowedValue = 0L; 1561 for (final String s : minIntValues) 1562 { 1563 try 1564 { 1565 minAllowedValue = 1566 Math.max(minAllowedValue, Long.parseLong(s)); 1567 } 1568 catch (final Exception e) 1569 { 1570 Debug.debugException(e); 1571 } 1572 } 1573 1574 if (longValue < minAllowedValue) 1575 { 1576 entryValid = false; 1577 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1578 if (invalidReasons != null) 1579 { 1580 invalidReasons.add(ERR_ENTRY_ATTR_VALUE_INT_TOO_SMALL.get( 1581 longValue, d.getNameOrOID(), minAllowedValue)); 1582 } 1583 } 1584 } 1585 catch (final Exception e) 1586 { 1587 Debug.debugException(e); 1588 entryValid = false; 1589 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1590 if (invalidReasons != null) 1591 { 1592 invalidReasons.add(ERR_ENTRY_ATTR_VALUE_NOT_INT.get( 1593 v.stringValue(), d.getNameOrOID(), "X-MIN-INT-VALUE")); 1594 } 1595 } 1596 } 1597 1598 1599 // If the attribute type definition includes an X-MAX-INT-VALUE 1600 // extension, then make sure the value is large enough. 1601 final String[] maxIntValues = extensions.get("X-MAX-INT-VALUE"); 1602 if (maxIntValues != null) 1603 { 1604 try 1605 { 1606 final long longValue = Long.parseLong(v.stringValue()); 1607 1608 long maxAllowedValue = Long.MAX_VALUE; 1609 for (final String s : maxIntValues) 1610 { 1611 try 1612 { 1613 maxAllowedValue = 1614 Math.min(maxAllowedValue, Long.parseLong(s)); 1615 } 1616 catch (final Exception e) 1617 { 1618 Debug.debugException(e); 1619 } 1620 } 1621 1622 if (longValue > maxAllowedValue) 1623 { 1624 entryValid = false; 1625 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1626 if (invalidReasons != null) 1627 { 1628 invalidReasons.add(ERR_ENTRY_ATTR_VALUE_INT_TOO_LARGE.get( 1629 longValue, d.getNameOrOID(), maxAllowedValue)); 1630 } 1631 } 1632 } 1633 catch (final Exception e) 1634 { 1635 Debug.debugException(e); 1636 entryValid = false; 1637 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1638 if (invalidReasons != null) 1639 { 1640 invalidReasons.add(ERR_ENTRY_ATTR_VALUE_NOT_INT.get( 1641 v.stringValue(), d.getNameOrOID(), "X-MAX-INT-VALUE")); 1642 } 1643 } 1644 } 1645 } 1646 1647 1648 // If the attribute type definition includes an X-MIN-VALUE-COUNT 1649 // extension, then make sure the value has enough values. 1650 final String[] minValueCounts = extensions.get("X-MIN-VALUE-COUNT"); 1651 if (minValueCounts != null) 1652 { 1653 int minValueCount = 0; 1654 for (final String s : minValueCounts) 1655 { 1656 try 1657 { 1658 minValueCount = Math.max(minValueCount, Integer.parseInt(s)); 1659 } 1660 catch (final Exception e) 1661 { 1662 Debug.debugException(e); 1663 } 1664 } 1665 1666 if (rawValues.length < minValueCount) 1667 { 1668 entryValid = false; 1669 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1670 if (invalidReasons != null) 1671 { 1672 invalidReasons.add(ERR_ENTRY_TOO_FEW_VALUES.get(rawValues.length, 1673 d.getNameOrOID(), minValueCount)); 1674 } 1675 } 1676 } 1677 1678 1679 // If the attribute type definition includes an X-MAX-VALUE-COUNT 1680 // extension, then make sure the value has enough values. 1681 final String[] maxValueCounts = extensions.get("X-MAX-VALUE-COUNT"); 1682 if (maxValueCounts != null) 1683 { 1684 int maxValueCount = Integer.MAX_VALUE; 1685 for (final String s : maxValueCounts) 1686 { 1687 try 1688 { 1689 maxValueCount = Math.min(maxValueCount, Integer.parseInt(s)); 1690 } 1691 catch (final Exception e) 1692 { 1693 Debug.debugException(e); 1694 } 1695 } 1696 1697 if (rawValues.length > maxValueCount) 1698 { 1699 entryValid = false; 1700 updateCount(d.getNameOrOID(), attributesViolatingSyntax); 1701 if (invalidReasons != null) 1702 { 1703 invalidReasons.add(ERR_ENTRY_TOO_MANY_VALUES.get(rawValues.length, 1704 d.getNameOrOID(), maxValueCount)); 1705 } 1706 } 1707 } 1708 } 1709 } 1710 1711 return entryValid; 1712 } 1713 1714 1715 1716 /** 1717 * Ensures that all of the auxiliary object classes contained in the object 1718 * class set are allowed by the provided DIT content rule. 1719 * 1720 * @param ocSet The set of object classes contained in the entry. 1721 * @param ditContentRule The DIT content rule to use to make the 1722 * determination. 1723 * @param invalidReasons A list to which messages may be added which provide 1724 * information about why the entry is invalid. It may 1725 * be {@code null} if this information is not needed. 1726 * 1727 * @return {@code true} if the entry passes all checks performed by this 1728 * method, or {@code false} if not. 1729 */ 1730 private boolean checkAuxiliaryClasses( 1731 @NotNull final HashSet<ObjectClassDefinition> ocSet, 1732 @NotNull final DITContentRuleDefinition ditContentRule, 1733 @Nullable final List<String> invalidReasons) 1734 { 1735 final HashSet<ObjectClassDefinition> auxSet = 1736 new HashSet<>(StaticUtils.computeMapCapacity(20)); 1737 for (final String s : ditContentRule.getAuxiliaryClasses()) 1738 { 1739 final ObjectClassDefinition d = schema.getObjectClass(s); 1740 if (d != null) 1741 { 1742 auxSet.add(d); 1743 } 1744 } 1745 1746 boolean entryValid = true; 1747 for (final ObjectClassDefinition d : ocSet) 1748 { 1749 final ObjectClassType t = d.getObjectClassType(schema); 1750 if ((t == ObjectClassType.AUXILIARY) && (! auxSet.contains(d))) 1751 { 1752 entryValid = false; 1753 updateCount(d.getNameOrOID(), prohibitedObjectClasses); 1754 if (invalidReasons != null) 1755 { 1756 invalidReasons.add( 1757 ERR_ENTRY_AUX_CLASS_NOT_ALLOWED.get(d.getNameOrOID())); 1758 } 1759 } 1760 } 1761 1762 return entryValid; 1763 } 1764 1765 1766 1767 /** 1768 * Ensures that the provided RDN is acceptable. It will ensure that all 1769 * attributes are defined in the schema and allowed for the entry, and that 1770 * the entry optionally conforms to the associated name form. 1771 * 1772 * @param rdn The RDN to examine. 1773 * @param entry The entry to examine. 1774 * @param requiredAttrs The set of attribute types which are required to be 1775 * included in the entry. 1776 * @param optionalAttrs The set of attribute types which may optionally be 1777 * included in the entry. 1778 * @param nameForm The name for to use to make the determination, if 1779 * defined. 1780 * @param invalidReasons A list to which messages may be added which provide 1781 * information about why the entry is invalid. It may 1782 * be {@code null} if this information is not needed. 1783 * 1784 * @return {@code true} if the entry passes all checks performed by this 1785 * method, or {@code false} if not. 1786 */ 1787 private boolean checkRDN(@NotNull final RDN rdn, @NotNull final Entry entry, 1788 @NotNull final Set<AttributeTypeDefinition> requiredAttrs, 1789 @NotNull final Set<AttributeTypeDefinition> optionalAttrs, 1790 @Nullable final NameFormDefinition nameForm, 1791 @Nullable final List<String> invalidReasons) 1792 { 1793 final HashSet<AttributeTypeDefinition> nfReqAttrs = 1794 new HashSet<>(StaticUtils.computeMapCapacity(5)); 1795 final HashSet<AttributeTypeDefinition> nfAllowedAttrs = 1796 new HashSet<>(StaticUtils.computeMapCapacity(5)); 1797 if (nameForm != null) 1798 { 1799 for (final String s : nameForm.getRequiredAttributes()) 1800 { 1801 final AttributeTypeDefinition d = schema.getAttributeType(s); 1802 if (d != null) 1803 { 1804 nfReqAttrs.add(d); 1805 } 1806 } 1807 1808 nfAllowedAttrs.addAll(nfReqAttrs); 1809 for (final String s : nameForm.getOptionalAttributes()) 1810 { 1811 final AttributeTypeDefinition d = schema.getAttributeType(s); 1812 if (d != null) 1813 { 1814 nfAllowedAttrs.add(d); 1815 } 1816 } 1817 } 1818 1819 boolean entryValid = true; 1820 final String[] attributeNames = rdn.getAttributeNames(); 1821 final byte[][] attributeValues = rdn.getByteArrayAttributeValues(); 1822 for (int i=0; i < attributeNames.length; i++) 1823 { 1824 final String name = attributeNames[i]; 1825 if (checkEntryMissingRDNValues) 1826 { 1827 final byte[] value = attributeValues[i]; 1828 final MatchingRule matchingRule = 1829 MatchingRule.selectEqualityMatchingRule(name, schema); 1830 if (! entry.hasAttributeValue(name, value, matchingRule)) 1831 { 1832 entryValid = false; 1833 entriesMissingRDNValues.incrementAndGet(); 1834 if (invalidReasons != null) 1835 { 1836 invalidReasons.add(ERR_ENTRY_MISSING_RDN_VALUE.get( 1837 rdn.getAttributeValues()[i], name)); 1838 } 1839 } 1840 } 1841 1842 final AttributeTypeDefinition d = schema.getAttributeType(name); 1843 if (d == null) 1844 { 1845 if (checkUndefinedAttributes) 1846 { 1847 entryValid = false; 1848 updateCount(name, undefinedAttributes); 1849 if (invalidReasons != null) 1850 { 1851 invalidReasons.add(ERR_ENTRY_RDN_ATTR_NOT_DEFINED.get(name)); 1852 } 1853 } 1854 } 1855 else 1856 { 1857 if (checkProhibitedAttributes && 1858 (! (requiredAttrs.contains(d) || optionalAttrs.contains(d) || 1859 d.isOperational()))) 1860 { 1861 entryValid = false; 1862 updateCount(d.getNameOrOID(), prohibitedAttributes); 1863 if (invalidReasons != null) 1864 { 1865 invalidReasons.add(ERR_ENTRY_RDN_ATTR_NOT_ALLOWED_IN_ENTRY.get( 1866 d.getNameOrOID())); 1867 } 1868 } 1869 1870 if (checkNameForms && (nameForm != null)) 1871 { 1872 if (! nfReqAttrs.remove(d)) 1873 { 1874 if (! nfAllowedAttrs.contains(d)) 1875 { 1876 if (entryValid) 1877 { 1878 entryValid = false; 1879 nameFormViolations.incrementAndGet(); 1880 } 1881 if (invalidReasons != null) 1882 { 1883 invalidReasons.add( 1884 ERR_ENTRY_RDN_ATTR_NOT_ALLOWED_BY_NF.get(name)); 1885 } 1886 } 1887 } 1888 } 1889 } 1890 } 1891 1892 if (checkNameForms && (! nfReqAttrs.isEmpty())) 1893 { 1894 if (entryValid) 1895 { 1896 entryValid = false; 1897 nameFormViolations.incrementAndGet(); 1898 } 1899 if (invalidReasons != null) 1900 { 1901 for (final AttributeTypeDefinition d : nfReqAttrs) 1902 { 1903 invalidReasons.add(ERR_ENTRY_RDN_MISSING_REQUIRED_ATTR.get( 1904 d.getNameOrOID())); 1905 } 1906 } 1907 } 1908 1909 return entryValid; 1910 } 1911 1912 1913 1914 /** 1915 * Updates the count for the given key in the provided map, adding a new key 1916 * with a count of one if necessary. 1917 * 1918 * @param key The key for which the count is to be updated. 1919 * @param map The map in which the update is to be made. 1920 */ 1921 private static void updateCount(@NotNull final String key, 1922 @NotNull final ConcurrentHashMap<String,AtomicLong> map) 1923 { 1924 final String lowerKey = StaticUtils.toLowerCase(key); 1925 AtomicLong l = map.get(lowerKey); 1926 if (l == null) 1927 { 1928 l = map.putIfAbsent(lowerKey, new AtomicLong(1L)); 1929 if (l == null) 1930 { 1931 return; 1932 } 1933 } 1934 1935 l.incrementAndGet(); 1936 } 1937 1938 1939 1940 /** 1941 * Resets all counts maintained by this entry validator. 1942 */ 1943 public void resetCounts() 1944 { 1945 entriesExamined.set(0L); 1946 entriesMissingRDNValues.set(0L); 1947 invalidEntries.set(0L); 1948 malformedDNs.set(0L); 1949 missingSuperiorClasses.set(0L); 1950 multipleStructuralClasses.set(0L); 1951 nameFormViolations.set(0L); 1952 noObjectClasses.set(0L); 1953 noStructuralClass.set(0L); 1954 1955 attributesViolatingSyntax.clear(); 1956 missingAttributes.clear(); 1957 prohibitedAttributes.clear(); 1958 prohibitedObjectClasses.clear(); 1959 singleValueViolations.clear(); 1960 undefinedAttributes.clear(); 1961 undefinedObjectClasses.clear(); 1962 } 1963 1964 1965 1966 /** 1967 * Retrieves the total number of entries examined during processing. 1968 * 1969 * @return The total number of entries examined during processing. 1970 */ 1971 public long getEntriesExamined() 1972 { 1973 return entriesExamined.get(); 1974 } 1975 1976 1977 1978 /** 1979 * Retrieves the total number of invalid entries encountered during 1980 * processing. 1981 * 1982 * @return The total number of invalid entries encountered during processing. 1983 */ 1984 public long getInvalidEntries() 1985 { 1986 return invalidEntries.get(); 1987 } 1988 1989 1990 1991 /** 1992 * Retrieves the total number of entries examined that had malformed DNs which 1993 * could not be parsed. 1994 * 1995 * @return The total number of entries examined that had malformed DNs. 1996 */ 1997 public long getMalformedDNs() 1998 { 1999 return malformedDNs.get(); 2000 } 2001 2002 2003 2004 /** 2005 * Retrieves the total number of entries examined that included an attribute 2006 * value in the RDN that was not present in the entry attributes. 2007 * 2008 * @return The total number of entries examined that included an attribute 2009 * value in the RDN that was not present in the entry attributes. 2010 */ 2011 public long getEntriesMissingRDNValues() 2012 { 2013 return entriesMissingRDNValues.get(); 2014 } 2015 2016 2017 2018 /** 2019 * Retrieves the total number of entries examined which did not contain any 2020 * object classes. 2021 * 2022 * @return The total number of entries examined which did not contain any 2023 * object classes. 2024 */ 2025 public long getEntriesWithoutAnyObjectClasses() 2026 { 2027 return noObjectClasses.get(); 2028 } 2029 2030 2031 2032 /** 2033 * Retrieves the total number of entries examined which did not contain any 2034 * structural object class. 2035 * 2036 * @return The total number of entries examined which did not contain any 2037 * structural object class. 2038 */ 2039 public long getEntriesMissingStructuralObjectClass() 2040 { 2041 return noStructuralClass.get(); 2042 } 2043 2044 2045 2046 /** 2047 * Retrieves the total number of entries examined which contained more than 2048 * one structural object class. 2049 * 2050 * @return The total number of entries examined which contained more than one 2051 * structural object class. 2052 */ 2053 public long getEntriesWithMultipleStructuralObjectClasses() 2054 { 2055 return multipleStructuralClasses.get(); 2056 } 2057 2058 2059 2060 /** 2061 * Retrieves the total number of entries examined which were missing one or 2062 * more superior object classes. 2063 * 2064 * @return The total number of entries examined which were missing one or 2065 * more superior object classes. 2066 */ 2067 public long getEntriesWithMissingSuperiorObjectClasses() 2068 { 2069 return missingSuperiorClasses.get(); 2070 } 2071 2072 2073 2074 /** 2075 * Retrieves the total number of entries examined which contained an RDN that 2076 * violated the constraints of the associated name form. 2077 * 2078 * @return The total number of entries examined which contained an RDN that 2079 * violated the constraints of the associated name form. 2080 */ 2081 public long getNameFormViolations() 2082 { 2083 return nameFormViolations.get(); 2084 } 2085 2086 2087 2088 /** 2089 * Retrieves the total number of undefined object classes encountered while 2090 * examining entries. Note that this number may be greater than the total 2091 * number of entries examined if entries contain multiple undefined object 2092 * classes. 2093 * 2094 * @return The total number of undefined object classes encountered while 2095 * examining entries. 2096 */ 2097 public long getTotalUndefinedObjectClasses() 2098 { 2099 return getMapTotal(undefinedObjectClasses); 2100 } 2101 2102 2103 2104 /** 2105 * Retrieves the undefined object classes encountered while processing 2106 * entries, mapped from the name of the undefined object class to the number 2107 * of entries in which that object class was referenced. 2108 * 2109 * @return The undefined object classes encountered while processing entries. 2110 */ 2111 @NotNull() 2112 public Map<String,Long> getUndefinedObjectClasses() 2113 { 2114 return convertMap(undefinedObjectClasses); 2115 } 2116 2117 2118 2119 /** 2120 * Retrieves the total number of undefined attribute types encountered while 2121 * examining entries. Note that this number may be greater than the total 2122 * number of entries examined if entries contain multiple undefined attribute 2123 * types. 2124 * 2125 * @return The total number of undefined attribute types encountered while 2126 * examining entries. 2127 */ 2128 public long getTotalUndefinedAttributes() 2129 { 2130 return getMapTotal(undefinedAttributes); 2131 } 2132 2133 2134 2135 /** 2136 * Retrieves the undefined attribute types encountered while processing 2137 * entries, mapped from the name of the undefined attribute to the number 2138 * of entries in which that attribute type was referenced. 2139 * 2140 * @return The undefined attribute types encountered while processing 2141 * entries. 2142 */ 2143 @NotNull() 2144 public Map<String,Long> getUndefinedAttributes() 2145 { 2146 return convertMap(undefinedAttributes); 2147 } 2148 2149 2150 2151 /** 2152 * Retrieves the total number of prohibited object classes encountered while 2153 * examining entries. Note that this number may be greater than the total 2154 * number of entries examined if entries contain multiple prohibited object 2155 * classes. 2156 * 2157 * @return The total number of prohibited object classes encountered while 2158 * examining entries. 2159 */ 2160 public long getTotalProhibitedObjectClasses() 2161 { 2162 return getMapTotal(prohibitedObjectClasses); 2163 } 2164 2165 2166 2167 /** 2168 * Retrieves the prohibited object classes encountered while processing 2169 * entries, mapped from the name of the object class to the number of entries 2170 * in which that object class was referenced. 2171 * 2172 * @return The prohibited object classes encountered while processing 2173 * entries. 2174 */ 2175 @NotNull() 2176 public Map<String,Long> getProhibitedObjectClasses() 2177 { 2178 return convertMap(prohibitedObjectClasses); 2179 } 2180 2181 2182 2183 /** 2184 * Retrieves the total number of prohibited attributes encountered while 2185 * examining entries. Note that this number may be greater than the total 2186 * number of entries examined if entries contain multiple prohibited 2187 * attributes. 2188 * 2189 * @return The total number of prohibited attributes encountered while 2190 * examining entries. 2191 */ 2192 public long getTotalProhibitedAttributes() 2193 { 2194 return getMapTotal(prohibitedAttributes); 2195 } 2196 2197 2198 2199 /** 2200 * Retrieves the prohibited attributes encountered while processing entries, 2201 * mapped from the name of the attribute to the number of entries in which 2202 * that attribute was referenced. 2203 * 2204 * @return The prohibited attributes encountered while processing entries. 2205 */ 2206 @NotNull() 2207 public Map<String,Long> getProhibitedAttributes() 2208 { 2209 return convertMap(prohibitedAttributes); 2210 } 2211 2212 2213 2214 /** 2215 * Retrieves the total number of missing required attributes encountered while 2216 * examining entries. Note that this number may be greater than the total 2217 * number of entries examined if entries are missing multiple attributes. 2218 * 2219 * @return The total number of missing required attributes encountered while 2220 * examining entries. 2221 */ 2222 public long getTotalMissingAttributes() 2223 { 2224 return getMapTotal(missingAttributes); 2225 } 2226 2227 2228 2229 /** 2230 * Retrieves the missing required encountered while processing entries, mapped 2231 * from the name of the attribute to the number of entries in which that 2232 * attribute was required but not found. 2233 * 2234 * @return The prohibited attributes encountered while processing entries. 2235 */ 2236 @NotNull() 2237 public Map<String,Long> getMissingAttributes() 2238 { 2239 return convertMap(missingAttributes); 2240 } 2241 2242 2243 2244 /** 2245 * Retrieves the total number of attribute values which violate their 2246 * associated syntax that were encountered while examining entries. Note that 2247 * this number may be greater than the total number of entries examined if 2248 * entries contain multiple malformed attribute values. 2249 * 2250 * @return The total number of attribute values which violate their 2251 * associated syntax that were encountered while examining entries. 2252 */ 2253 public long getTotalAttributesViolatingSyntax() 2254 { 2255 return getMapTotal(attributesViolatingSyntax); 2256 } 2257 2258 2259 2260 /** 2261 * Retrieves the attributes with values violating their associated syntax that 2262 * were encountered while processing entries, mapped from the name of the 2263 * attribute to the number of malformed values found for that attribute. 2264 * 2265 * @return The attributes with malformed values encountered while processing 2266 * entries. 2267 */ 2268 @NotNull() 2269 public Map<String,Long> getAttributesViolatingSyntax() 2270 { 2271 return convertMap(attributesViolatingSyntax); 2272 } 2273 2274 2275 2276 /** 2277 * Retrieves the total number of attributes defined as single-valued that 2278 * contained multiple values which were encountered while processing entries. 2279 * Note that this number may be greater than the total number of entries 2280 * examined if entries contain multiple such attributes. 2281 * 2282 * @return The total number of attribute defined as single-valued that 2283 * contained multiple values which were encountered while processing 2284 * entries. 2285 */ 2286 public long getTotalSingleValueViolations() 2287 { 2288 return getMapTotal(singleValueViolations); 2289 } 2290 2291 2292 2293 /** 2294 * Retrieves the attributes defined as single-valued that contained multiple 2295 * values which were encountered while processing entries, mapped from the 2296 * name of the attribute to the number of entries in which that attribute had 2297 * multiple values. 2298 * 2299 * @return The attributes defined as single-valued that contained multiple 2300 * values which were encountered while processing entries. 2301 */ 2302 @NotNull() 2303 public Map<String,Long> getSingleValueViolations() 2304 { 2305 return convertMap(singleValueViolations); 2306 } 2307 2308 2309 2310 /** 2311 * Retrieves the total number of occurrences for all items in the provided 2312 * map. 2313 * 2314 * @param map The map to be processed. 2315 * 2316 * @return The total number of occurrences for all items in the provided map. 2317 */ 2318 private static long getMapTotal(@NotNull final Map<String,AtomicLong> map) 2319 { 2320 long total = 0L; 2321 2322 for (final AtomicLong l : map.values()) 2323 { 2324 total += l.longValue(); 2325 } 2326 2327 return total; 2328 } 2329 2330 2331 2332 /** 2333 * Converts the provided map from strings to atomic longs to a map from 2334 * strings to longs. 2335 * 2336 * @param map The map to be processed. 2337 * 2338 * @return The new map. 2339 */ 2340 @NotNull() 2341 private static Map<String,Long> convertMap( 2342 @NotNull final Map<String,AtomicLong> map) 2343 { 2344 final TreeMap<String,Long> m = new TreeMap<>(); 2345 for (final Map.Entry<String,AtomicLong> e : map.entrySet()) 2346 { 2347 m.put(e.getKey(), e.getValue().longValue()); 2348 } 2349 2350 return Collections.unmodifiableMap(m); 2351 } 2352 2353 2354 2355 /** 2356 * Retrieves a list of messages providing a summary of the invalid entries 2357 * processed by this class. 2358 * 2359 * @param detailedResults Indicates whether to include detailed information 2360 * about the attributes and object classes 2361 * responsible for the violations. 2362 * 2363 * @return A list of messages providing a summary of the invalid entries 2364 * processed by this class, or an empty list if all entries examined 2365 * were valid. 2366 */ 2367 @NotNull() 2368 public List<String> getInvalidEntrySummary(final boolean detailedResults) 2369 { 2370 final long numInvalid = invalidEntries.get(); 2371 if (numInvalid == 0) 2372 { 2373 return Collections.emptyList(); 2374 } 2375 2376 final ArrayList<String> messages = new ArrayList<>(5); 2377 final long numEntries = entriesExamined.get(); 2378 long pct = 100 * numInvalid / numEntries; 2379 messages.add(INFO_ENTRY_INVALID_ENTRY_COUNT.get( 2380 numInvalid, numEntries, pct)); 2381 2382 final long numBadDNs = malformedDNs.get(); 2383 if (numBadDNs > 0) 2384 { 2385 pct = 100 * numBadDNs / numEntries; 2386 messages.add(INFO_ENTRY_MALFORMED_DN_COUNT.get( 2387 numBadDNs, numEntries, pct)); 2388 } 2389 2390 final long numEntriesMissingRDNValues = entriesMissingRDNValues.get(); 2391 if (numEntriesMissingRDNValues > 0) 2392 { 2393 pct = 100* numEntriesMissingRDNValues / numEntries; 2394 messages.add(INFO_ENTRY_MISSING_RDN_VALUE_COUNT.get( 2395 numEntriesMissingRDNValues, numEntries, pct)); 2396 } 2397 2398 final long numNoOCs = noObjectClasses.get(); 2399 if (numNoOCs > 0) 2400 { 2401 pct = 100 * numNoOCs / numEntries; 2402 messages.add(INFO_ENTRY_NO_OC_COUNT.get(numNoOCs, numEntries, pct)); 2403 } 2404 2405 final long numMissingStructural = noStructuralClass.get(); 2406 if (numMissingStructural > 0) 2407 { 2408 pct = 100 * numMissingStructural / numEntries; 2409 messages.add(INFO_ENTRY_NO_STRUCTURAL_OC_COUNT.get( 2410 numMissingStructural, numEntries, pct)); 2411 } 2412 2413 final long numMultipleStructural = multipleStructuralClasses.get(); 2414 if (numMultipleStructural > 0) 2415 { 2416 pct = 100 * numMultipleStructural / numEntries; 2417 messages.add(INFO_ENTRY_MULTIPLE_STRUCTURAL_OCS_COUNT.get( 2418 numMultipleStructural, numEntries, pct)); 2419 } 2420 2421 final long numNFViolations = nameFormViolations.get(); 2422 if (numNFViolations > 0) 2423 { 2424 pct = 100 * numNFViolations / numEntries; 2425 messages.add(INFO_ENTRY_NF_VIOLATION_COUNT.get( 2426 numNFViolations, numEntries, pct)); 2427 } 2428 2429 final long numUndefinedOCs = getTotalUndefinedObjectClasses(); 2430 if (numUndefinedOCs > 0) 2431 { 2432 messages.add(INFO_ENTRY_UNDEFINED_OC_COUNT.get(numUndefinedOCs)); 2433 if (detailedResults) 2434 { 2435 for (final Map.Entry<String,AtomicLong> e : 2436 undefinedObjectClasses.entrySet()) 2437 { 2438 messages.add(INFO_ENTRY_UNDEFINED_OC_NAME_COUNT.get( 2439 e.getKey(), e.getValue().longValue())); 2440 } 2441 } 2442 } 2443 2444 final long numProhibitedOCs = getTotalProhibitedObjectClasses(); 2445 if (numProhibitedOCs > 0) 2446 { 2447 messages.add(INFO_ENTRY_PROHIBITED_OC_COUNT.get(numProhibitedOCs)); 2448 if (detailedResults) 2449 { 2450 for (final Map.Entry<String,AtomicLong> e : 2451 prohibitedObjectClasses.entrySet()) 2452 { 2453 messages.add(INFO_ENTRY_PROHIBITED_OC_NAME_COUNT.get( 2454 e.getKey(), e.getValue().longValue())); 2455 } 2456 } 2457 } 2458 2459 final long numMissingSuperior = 2460 getEntriesWithMissingSuperiorObjectClasses(); 2461 if (numMissingSuperior > 0) 2462 { 2463 messages.add( 2464 INFO_ENTRY_MISSING_SUPERIOR_OC_COUNT.get(numMissingSuperior)); 2465 } 2466 2467 final long numUndefinedAttrs = getTotalUndefinedAttributes(); 2468 if (numUndefinedAttrs > 0) 2469 { 2470 messages.add(INFO_ENTRY_UNDEFINED_ATTR_COUNT.get(numUndefinedAttrs)); 2471 if (detailedResults) 2472 { 2473 for (final Map.Entry<String,AtomicLong> e : 2474 undefinedAttributes.entrySet()) 2475 { 2476 messages.add(INFO_ENTRY_UNDEFINED_ATTR_NAME_COUNT.get( 2477 e.getKey(), e.getValue().longValue())); 2478 } 2479 } 2480 } 2481 2482 final long numMissingAttrs = getTotalMissingAttributes(); 2483 if (numMissingAttrs > 0) 2484 { 2485 messages.add(INFO_ENTRY_MISSING_ATTR_COUNT.get(numMissingAttrs)); 2486 if (detailedResults) 2487 { 2488 for (final Map.Entry<String,AtomicLong> e : 2489 missingAttributes.entrySet()) 2490 { 2491 messages.add(INFO_ENTRY_MISSING_ATTR_NAME_COUNT.get( 2492 e.getKey(), e.getValue().longValue())); 2493 } 2494 } 2495 } 2496 2497 final long numProhibitedAttrs = getTotalProhibitedAttributes(); 2498 if (numProhibitedAttrs > 0) 2499 { 2500 messages.add(INFO_ENTRY_PROHIBITED_ATTR_COUNT.get(numProhibitedAttrs)); 2501 if (detailedResults) 2502 { 2503 for (final Map.Entry<String,AtomicLong> e : 2504 prohibitedAttributes.entrySet()) 2505 { 2506 messages.add(INFO_ENTRY_PROHIBITED_ATTR_NAME_COUNT.get( 2507 e.getKey(), e.getValue().longValue())); 2508 } 2509 } 2510 } 2511 2512 final long numSingleValuedViolations = getTotalSingleValueViolations(); 2513 if (numSingleValuedViolations > 0) 2514 { 2515 messages.add(INFO_ENTRY_SINGLE_VALUE_VIOLATION_COUNT.get( 2516 numSingleValuedViolations)); 2517 if (detailedResults) 2518 { 2519 for (final Map.Entry<String,AtomicLong> e : 2520 singleValueViolations.entrySet()) 2521 { 2522 messages.add(INFO_ENTRY_SINGLE_VALUE_VIOLATION_NAME_COUNT.get( 2523 e.getKey(), e.getValue().longValue())); 2524 } 2525 } 2526 } 2527 2528 final long numSyntaxViolations = getTotalAttributesViolatingSyntax(); 2529 if (numSyntaxViolations > 0) 2530 { 2531 messages.add(INFO_ENTRY_SYNTAX_VIOLATION_COUNT.get(numSyntaxViolations)); 2532 if (detailedResults) 2533 { 2534 for (final Map.Entry<String,AtomicLong> e : 2535 attributesViolatingSyntax.entrySet()) 2536 { 2537 messages.add(INFO_ENTRY_SYNTAX_VIOLATION_NAME_COUNT.get( 2538 e.getKey(), e.getValue().longValue())); 2539 } 2540 } 2541 } 2542 2543 return Collections.unmodifiableList(messages); 2544 } 2545}