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