001/* 002 * Copyright 2013-2015 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2013-2015 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.examples; 022 023 024 025import java.io.OutputStream; 026import java.util.Collections; 027import java.util.LinkedHashMap; 028import java.util.LinkedHashSet; 029import java.util.List; 030import java.util.Map; 031import java.util.TreeMap; 032import java.util.concurrent.atomic.AtomicLong; 033 034import com.unboundid.asn1.ASN1OctetString; 035import com.unboundid.ldap.sdk.Attribute; 036import com.unboundid.ldap.sdk.DereferencePolicy; 037import com.unboundid.ldap.sdk.DN; 038import com.unboundid.ldap.sdk.Filter; 039import com.unboundid.ldap.sdk.LDAPConnection; 040import com.unboundid.ldap.sdk.LDAPException; 041import com.unboundid.ldap.sdk.LDAPSearchException; 042import com.unboundid.ldap.sdk.ResultCode; 043import com.unboundid.ldap.sdk.SearchRequest; 044import com.unboundid.ldap.sdk.SearchResult; 045import com.unboundid.ldap.sdk.SearchResultEntry; 046import com.unboundid.ldap.sdk.SearchResultReference; 047import com.unboundid.ldap.sdk.SearchResultListener; 048import com.unboundid.ldap.sdk.SearchScope; 049import com.unboundid.ldap.sdk.Version; 050import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl; 051import com.unboundid.util.Debug; 052import com.unboundid.util.LDAPCommandLineTool; 053import com.unboundid.util.StaticUtils; 054import com.unboundid.util.ThreadSafety; 055import com.unboundid.util.ThreadSafetyLevel; 056import com.unboundid.util.args.ArgumentException; 057import com.unboundid.util.args.ArgumentParser; 058import com.unboundid.util.args.DNArgument; 059import com.unboundid.util.args.FilterArgument; 060import com.unboundid.util.args.IntegerArgument; 061import com.unboundid.util.args.StringArgument; 062 063 064 065/** 066 * This class provides a tool that may be used to identify unique attribute 067 * conflicts (i.e., attributes which are supposed to be unique but for which 068 * some values exist in multiple entries). 069 * <BR><BR> 070 * All of the necessary information is provided using command line arguments. 071 * Supported arguments include those allowed by the {@link LDAPCommandLineTool} 072 * class, as well as the following additional arguments: 073 * <UL> 074 * <LI>"-b {baseDN}" or "--baseDN {baseDN}" -- specifies the base DN to use 075 * for the searches. At least one base DN must be provided.</LI> 076 * <LI>"-f" {filter}" or "--filter "{filter}" -- specifies an optional 077 * filter to use for identifying entries across which uniqueness should be 078 * enforced. If this is not provided, then all entries containing the 079 * target attribute(s) will be examined.</LI> 080 * <LI>"-A {attribute}" or "--attribute {attribute}" -- specifies an attribute 081 * for which to enforce uniqueness. At least one unique attribute must be 082 * provided.</LI> 083 * <LI>"-m {behavior}" or "--multipleAttributeBehavior {behavior}" -- 084 * specifies the behavior that the tool should exhibit if multiple 085 * unique attributes are provided. Allowed values include 086 * unique-within-each-attribute, 087 * unique-across-all-attributes-including-in-same-entry, and 088 * unique-across-all-attributes-except-in-same-entry.</LI> 089 * <LI>"-z {size}" or "--simplePageSize {size}" -- indicates that the search 090 * to find entries with unique attributes should use the simple paged 091 * results control to iterate across entries in fixed-size pages rather 092 * than trying to use a single search to identify all entries containing 093 * unique attributes.</LI> 094 * </UL> 095 */ 096@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 097public final class IdentifyUniqueAttributeConflicts 098 extends LDAPCommandLineTool 099 implements SearchResultListener 100{ 101 /** 102 * The unique attribute behavior value that indicates uniqueness should only 103 * be ensured within each attribute. 104 */ 105 private static final String BEHAVIOR_UNIQUE_WITHIN_ATTR = 106 "unique-within-each-attribute"; 107 108 109 110 /** 111 * The unique attribute behavior value that indicates uniqueness should be 112 * ensured across all attributes, and conflicts will not be allowed across 113 * attributes in the same entry. 114 */ 115 private static final String BEHAVIOR_UNIQUE_ACROSS_ATTRS_INCLUDING_SAME = 116 "unique-across-all-attributes-including-in-same-entry"; 117 118 119 120 /** 121 * The unique attribute behavior value that indicates uniqueness should be 122 * ensured across all attributes, except that conflicts will not be allowed 123 * across attributes in the same entry. 124 */ 125 private static final String BEHAVIOR_UNIQUE_ACROSS_ATTRS_EXCEPT_SAME = 126 "unique-across-all-attributes-except-in-same-entry"; 127 128 129 130 /** 131 * The serial version UID for this serializable class. 132 */ 133 private static final long serialVersionUID = -7506817625818259323L; 134 135 136 137 // The number of entries examined so far. 138 private final AtomicLong entriesExamined; 139 140 // Indicates whether cross-attribute uniqueness conflicts should be allowed 141 // in the same entry. 142 private boolean allowConflictsInSameEntry; 143 144 // Indicates whether uniqueness should be enforced across all attributes 145 // rather than within each attribute. 146 private boolean uniqueAcrossAttributes; 147 148 // The argument used to specify the base DNs to use for searches. 149 private DNArgument baseDNArgument; 150 151 // The argument used to specify a filter indicating which entries to examine. 152 private FilterArgument filterArgument; 153 154 // The argument used to specify the search page size. 155 private IntegerArgument pageSizeArgument; 156 157 // The connection to use for finding unique attribute conflicts. 158 private LDAPConnection findConflictsConnection; 159 160 // A map with counts of unique attribute conflicts by attribute type. 161 private final Map<String, AtomicLong> conflictCounts; 162 163 // The names of the attributes for which to find uniqueness conflicts. 164 private String[] attributes; 165 166 // The set of base DNs to use for the searches. 167 private String[] baseDNs; 168 169 // The argument used to specify the attributes for which to find uniqueness 170 // conflicts. 171 private StringArgument attributeArgument; 172 173 // The argument used to specify the behavior that should be exhibited if 174 // multiple attributes are specified. 175 private StringArgument multipleAttributeBehaviorArgument; 176 177 178 179 /** 180 * Parse the provided command line arguments and perform the appropriate 181 * processing. 182 * 183 * @param args The command line arguments provided to this program. 184 */ 185 public static void main(final String... args) 186 { 187 final ResultCode resultCode = main(args, System.out, System.err); 188 if (resultCode != ResultCode.SUCCESS) 189 { 190 System.exit(resultCode.intValue()); 191 } 192 } 193 194 195 196 /** 197 * Parse the provided command line arguments and perform the appropriate 198 * processing. 199 * 200 * @param args The command line arguments provided to this program. 201 * @param outStream The output stream to which standard out should be 202 * written. It may be {@code null} if output should be 203 * suppressed. 204 * @param errStream The output stream to which standard error should be 205 * written. It may be {@code null} if error messages 206 * should be suppressed. 207 * 208 * @return A result code indicating whether the processing was successful. 209 */ 210 public static ResultCode main(final String[] args, 211 final OutputStream outStream, 212 final OutputStream errStream) 213 { 214 final IdentifyUniqueAttributeConflicts tool = 215 new IdentifyUniqueAttributeConflicts(outStream, errStream); 216 return tool.runTool(args); 217 } 218 219 220 221 /** 222 * Creates a new instance of this tool. 223 * 224 * @param outStream The output stream to which standard out should be 225 * written. It may be {@code null} if output should be 226 * suppressed. 227 * @param errStream The output stream to which standard error should be 228 * written. It may be {@code null} if error messages 229 * should be suppressed. 230 */ 231 public IdentifyUniqueAttributeConflicts(final OutputStream outStream, 232 final OutputStream errStream) 233 { 234 super(outStream, errStream); 235 236 baseDNArgument = null; 237 filterArgument = null; 238 pageSizeArgument = null; 239 attributeArgument = null; 240 multipleAttributeBehaviorArgument = null; 241 findConflictsConnection = null; 242 allowConflictsInSameEntry = false; 243 uniqueAcrossAttributes = false; 244 attributes = null; 245 baseDNs = null; 246 247 entriesExamined = new AtomicLong(0L); 248 conflictCounts = new TreeMap<String, AtomicLong>(); 249 } 250 251 252 253 /** 254 * Retrieves the name of this tool. It should be the name of the command used 255 * to invoke this tool. 256 * 257 * @return The name for this tool. 258 */ 259 @Override() 260 public String getToolName() 261 { 262 return "identify-unique-attribute-conflicts"; 263 } 264 265 266 267 /** 268 * Retrieves a human-readable description for this tool. 269 * 270 * @return A human-readable description for this tool. 271 */ 272 @Override() 273 public String getToolDescription() 274 { 275 return "This tool may be used to identify unique attribute conflicts. " + 276 "That is, it may identify values of one or more attributes which " + 277 "are supposed to exist only in a single entry but are found in " + 278 "multiple entries."; 279 } 280 281 282 283 /** 284 * Retrieves a version string for this tool, if available. 285 * 286 * @return A version string for this tool, or {@code null} if none is 287 * available. 288 */ 289 @Override() 290 public String getToolVersion() 291 { 292 return Version.NUMERIC_VERSION_STRING; 293 } 294 295 296 297 /** 298 * Adds the arguments needed by this command-line tool to the provided 299 * argument parser which are not related to connecting or authenticating to 300 * the directory server. 301 * 302 * @param parser The argument parser to which the arguments should be added. 303 * 304 * @throws ArgumentException If a problem occurs while adding the arguments. 305 */ 306 @Override() 307 public void addNonLDAPArguments(final ArgumentParser parser) 308 throws ArgumentException 309 { 310 String description = "The search base DN(s) to use to find entries with " + 311 "attributes for which to find uniqueness conflicts. At least one " + 312 "base DN must be specified."; 313 baseDNArgument = new DNArgument('b', "baseDN", true, 0, "{dn}", 314 description); 315 parser.addArgument(baseDNArgument); 316 317 description = "A filter that will be used to identify the set of " + 318 "entries in which to identify uniqueness conflicts. If this is not " + 319 "specified, then all entries containing the target attribute(s) " + 320 "will be examined."; 321 filterArgument = new FilterArgument('f', "filter", false, 1, "{filter}", 322 description); 323 parser.addArgument(filterArgument); 324 325 description = "The attribute(s) for which to find missing references. " + 326 "At least one attribute must be specified, and each attribute " + 327 "must be indexed for equality searches and have values which are DNs."; 328 attributeArgument = new StringArgument('A', "attribute", true, 0, "{attr}", 329 description); 330 parser.addArgument(attributeArgument); 331 332 description = "Indicates the behavior to exhibit if multiple unique " + 333 "attributes are provided. Allowed values are '" + 334 BEHAVIOR_UNIQUE_WITHIN_ATTR + "' (indicates that each value only " + 335 "needs to be unique within its own attribute type), '" + 336 BEHAVIOR_UNIQUE_ACROSS_ATTRS_INCLUDING_SAME + "' (indicates that " + 337 "each value needs to be unique across all of the specified " + 338 "attributes), and '" + BEHAVIOR_UNIQUE_ACROSS_ATTRS_EXCEPT_SAME + 339 "' (indicates each value needs to be unique across all of the " + 340 "specified attributes, except that multiple attributes in the same " + 341 "entry are allowed to share the same value)."; 342 final LinkedHashSet<String> allowedValues = new LinkedHashSet<String>(3); 343 allowedValues.add(BEHAVIOR_UNIQUE_WITHIN_ATTR); 344 allowedValues.add(BEHAVIOR_UNIQUE_ACROSS_ATTRS_INCLUDING_SAME); 345 allowedValues.add(BEHAVIOR_UNIQUE_ACROSS_ATTRS_EXCEPT_SAME); 346 multipleAttributeBehaviorArgument = new StringArgument('m', 347 "multipleAttributeBehavior", false, 1, "{behavior}", description, 348 allowedValues, BEHAVIOR_UNIQUE_WITHIN_ATTR); 349 parser.addArgument(multipleAttributeBehaviorArgument); 350 351 description = "The maximum number of entries to retrieve at a time when " + 352 "attempting to find entries with references to other entries. This " + 353 "requires that the authenticated user have permission to use the " + 354 "simple paged results control, but it can avoid problems with the " + 355 "server sending entries too quickly for the client to handle. By " + 356 "default, the simple paged results control will not be used."; 357 pageSizeArgument = 358 new IntegerArgument('z', "simplePageSize", false, 1, "{num}", 359 description, 1, Integer.MAX_VALUE); 360 parser.addArgument(pageSizeArgument); 361 } 362 363 364 365 /** 366 * Performs the core set of processing for this tool. 367 * 368 * @return A result code that indicates whether the processing completed 369 * successfully. 370 */ 371 @Override() 372 public ResultCode doToolProcessing() 373 { 374 // Determine the multi-attribute behavior that we should exhibit. 375 final List<String> attrList = attributeArgument.getValues(); 376 final String multiAttrBehavior = 377 multipleAttributeBehaviorArgument.getValue(); 378 if (attrList.size() > 1) 379 { 380 if (multiAttrBehavior.equalsIgnoreCase( 381 BEHAVIOR_UNIQUE_ACROSS_ATTRS_INCLUDING_SAME)) 382 { 383 uniqueAcrossAttributes = true; 384 allowConflictsInSameEntry = false; 385 } 386 else if (multiAttrBehavior.equalsIgnoreCase( 387 BEHAVIOR_UNIQUE_ACROSS_ATTRS_EXCEPT_SAME)) 388 { 389 uniqueAcrossAttributes = true; 390 allowConflictsInSameEntry = true; 391 } 392 else 393 { 394 uniqueAcrossAttributes = false; 395 allowConflictsInSameEntry = true; 396 } 397 } 398 else 399 { 400 uniqueAcrossAttributes = false; 401 allowConflictsInSameEntry = true; 402 } 403 404 405 // Get the string representations of the base DNs. 406 final List<DN> dnList = baseDNArgument.getValues(); 407 baseDNs = new String[dnList.size()]; 408 for (int i=0; i < baseDNs.length; i++) 409 { 410 baseDNs[i] = dnList.get(i).toString(); 411 } 412 413 // Establish a connection to the target directory server to use for finding 414 // entries with unique attributes. 415 final LDAPConnection findUniqueAttributesConnection; 416 try 417 { 418 findUniqueAttributesConnection = getConnection(); 419 } 420 catch (final LDAPException le) 421 { 422 Debug.debugException(le); 423 err("Unable to establish a connection to the directory server: ", 424 StaticUtils.getExceptionMessage(le)); 425 return le.getResultCode(); 426 } 427 428 try 429 { 430 // Establish a connection to use for finding unique attribute conflicts. 431 try 432 { 433 findConflictsConnection = getConnection(); 434 } 435 catch (final LDAPException le) 436 { 437 Debug.debugException(le); 438 err("Unable to establish a connection to the directory server: ", 439 StaticUtils.getExceptionMessage(le)); 440 return le.getResultCode(); 441 } 442 443 // Get the set of attributes for which to ensure uniqueness. 444 attributes = new String[attrList.size()]; 445 attrList.toArray(attributes); 446 447 448 // Construct a search filter that will be used to find all entries with 449 // unique attributes. 450 Filter filter; 451 if (attributes.length == 1) 452 { 453 filter = Filter.createPresenceFilter(attributes[0]); 454 conflictCounts.put(attributes[0], new AtomicLong(0L)); 455 } 456 else 457 { 458 final Filter[] orComps = new Filter[attributes.length]; 459 for (int i=0; i < attributes.length; i++) 460 { 461 orComps[i] = Filter.createPresenceFilter(attributes[i]); 462 conflictCounts.put(attributes[i], new AtomicLong(0L)); 463 } 464 filter = Filter.createORFilter(orComps); 465 } 466 467 if (filterArgument.isPresent()) 468 { 469 filter = Filter.createANDFilter(filterArgument.getValue(), filter); 470 } 471 472 473 // Iterate across all of the search base DNs and perform searches to find 474 // unique attributes. 475 for (final String baseDN : baseDNs) 476 { 477 ASN1OctetString cookie = null; 478 do 479 { 480 final SearchRequest searchRequest = new SearchRequest(this, baseDN, 481 SearchScope.SUB, filter, attributes); 482 if (pageSizeArgument.isPresent()) 483 { 484 searchRequest.addControl(new SimplePagedResultsControl( 485 pageSizeArgument.getValue(), cookie, false)); 486 } 487 488 SearchResult searchResult; 489 try 490 { 491 searchResult = findUniqueAttributesConnection.search(searchRequest); 492 } 493 catch (final LDAPSearchException lse) 494 { 495 Debug.debugException(lse); 496 searchResult = lse.getSearchResult(); 497 } 498 499 if (searchResult.getResultCode() != ResultCode.SUCCESS) 500 { 501 err("An error occurred while attempting to search for unique " + 502 "attributes in entries below " + baseDN + ": " + 503 searchResult.getDiagnosticMessage()); 504 return searchResult.getResultCode(); 505 } 506 507 final SimplePagedResultsControl pagedResultsResponse; 508 try 509 { 510 pagedResultsResponse = SimplePagedResultsControl.get(searchResult); 511 } 512 catch (final LDAPException le) 513 { 514 Debug.debugException(le); 515 err("An error occurred while attempting to decode a simple " + 516 "paged results response control in the response to a " + 517 "search for entries below " + baseDN + ": " + 518 StaticUtils.getExceptionMessage(le)); 519 return le.getResultCode(); 520 } 521 522 if (pagedResultsResponse != null) 523 { 524 if (pagedResultsResponse.moreResultsToReturn()) 525 { 526 cookie = pagedResultsResponse.getCookie(); 527 } 528 else 529 { 530 cookie = null; 531 } 532 } 533 } 534 while (cookie != null); 535 } 536 537 538 // See if there were any missing references found. 539 boolean conflictFound = false; 540 for (final Map.Entry<String,AtomicLong> e : conflictCounts.entrySet()) 541 { 542 final long numConflicts = e.getValue().get(); 543 if (numConflicts > 0L) 544 { 545 if (! conflictFound) 546 { 547 err(); 548 conflictFound = true; 549 } 550 551 err("Found " + numConflicts + 552 " unique value conflicts in attribute " + e.getKey()); 553 } 554 } 555 556 if (conflictFound) 557 { 558 return ResultCode.CONSTRAINT_VIOLATION; 559 } 560 else 561 { 562 out("No unique attribute conflicts were found."); 563 return ResultCode.SUCCESS; 564 } 565 } 566 finally 567 { 568 findUniqueAttributesConnection.close(); 569 570 if (findConflictsConnection != null) 571 { 572 findConflictsConnection.close(); 573 } 574 } 575 } 576 577 578 579 /** 580 * Retrieves a map that correlates the number of missing references found by 581 * attribute type. 582 * 583 * @return A map that correlates the number of missing references found by 584 * attribute type. 585 */ 586 public Map<String,AtomicLong> getConflictCounts() 587 { 588 return Collections.unmodifiableMap(conflictCounts); 589 } 590 591 592 593 /** 594 * Retrieves a set of information that may be used to generate example usage 595 * information. Each element in the returned map should consist of a map 596 * between an example set of arguments and a string that describes the 597 * behavior of the tool when invoked with that set of arguments. 598 * 599 * @return A set of information that may be used to generate example usage 600 * information. It may be {@code null} or empty if no example usage 601 * information is available. 602 */ 603 @Override() 604 public LinkedHashMap<String[],String> getExampleUsages() 605 { 606 final LinkedHashMap<String[],String> exampleMap = 607 new LinkedHashMap<String[],String>(1); 608 609 final String[] args = 610 { 611 "--hostname", "server.example.com", 612 "--port", "389", 613 "--bindDN", "uid=john.doe,ou=People,dc=example,dc=com", 614 "--bindPassword", "password", 615 "--baseDN", "dc=example,dc=com", 616 "--attribute", "uid", 617 "--simplePageSize", "100" 618 }; 619 exampleMap.put(args, 620 "Identify any values of the uid attribute that are not unique " + 621 "across all entries below dc=example,dc=com."); 622 623 return exampleMap; 624 } 625 626 627 628 /** 629 * Indicates that the provided search result entry has been returned by the 630 * server and may be processed by this search result listener. 631 * 632 * @param searchEntry The search result entry that has been returned by the 633 * server. 634 */ 635 public void searchEntryReturned(final SearchResultEntry searchEntry) 636 { 637 try 638 { 639 // If we need to check for conflicts in the same entry, then do that 640 // first. 641 if (! allowConflictsInSameEntry) 642 { 643 boolean conflictFound = false; 644 for (int i=0; i < attributes.length; i++) 645 { 646 final List<Attribute> l1 = 647 searchEntry.getAttributesWithOptions(attributes[i], null); 648 if (l1 != null) 649 { 650 for (int j=i+1; j < attributes.length; j++) 651 { 652 final List<Attribute> l2 = 653 searchEntry.getAttributesWithOptions(attributes[j], null); 654 if (l2 != null) 655 { 656 for (final Attribute a1 : l1) 657 { 658 for (final String value : a1.getValues()) 659 { 660 for (final Attribute a2 : l2) 661 { 662 if (a2.hasValue(value)) 663 { 664 err("Value '", value, "' in attribute ", a1.getName(), 665 " of entry '", searchEntry.getDN(), 666 " is also present in attribute ", a2.getName(), 667 " of the same entry."); 668 conflictFound = true; 669 conflictCounts.get(attributes[i]).incrementAndGet(); 670 } 671 } 672 } 673 } 674 } 675 } 676 } 677 } 678 679 if (conflictFound) 680 { 681 return; 682 } 683 } 684 685 686 // Get the unique attributes from the entry and search for conflicts with 687 // each value in other entries. Although we could theoretically do this 688 // with fewer searches, most uses of unique attributes don't have multiple 689 // values, so the following code (which is much simpler) is just as 690 // efficient in the common case. 691 for (final String attrName : attributes) 692 { 693 final List<Attribute> attrList = 694 searchEntry.getAttributesWithOptions(attrName, null); 695 for (final Attribute a : attrList) 696 { 697 for (final String value : a.getValues()) 698 { 699 Filter filter; 700 if (uniqueAcrossAttributes) 701 { 702 final Filter[] orComps = new Filter[attributes.length]; 703 for (int i=0; i < attributes.length; i++) 704 { 705 orComps[i] = Filter.createEqualityFilter(attributes[i], value); 706 } 707 filter = Filter.createORFilter(orComps); 708 } 709 else 710 { 711 filter = Filter.createEqualityFilter(attrName, value); 712 } 713 714 if (filterArgument.isPresent()) 715 { 716 filter = Filter.createANDFilter(filterArgument.getValue(), 717 filter); 718 } 719 720baseDNLoop: 721 for (final String baseDN : baseDNs) 722 { 723 SearchResult searchResult; 724 try 725 { 726 searchResult = findConflictsConnection.search(baseDN, 727 SearchScope.SUB, DereferencePolicy.NEVER, 2, 0, false, 728 filter, "1.1"); 729 } 730 catch (final LDAPSearchException lse) 731 { 732 Debug.debugException(lse); 733 searchResult = lse.getSearchResult(); 734 } 735 736 for (final SearchResultEntry e : searchResult.getSearchEntries()) 737 { 738 try 739 { 740 if (DN.equals(searchEntry.getDN(), e.getDN())) 741 { 742 continue; 743 } 744 } 745 catch (final Exception ex) 746 { 747 Debug.debugException(ex); 748 } 749 750 err("Value '", value, "' in attribute ", a.getName(), 751 " of entry '" + searchEntry.getDN(), 752 "' is also present in entry '", e.getDN(), "'."); 753 conflictCounts.get(attrName).incrementAndGet(); 754 break baseDNLoop; 755 } 756 757 if (searchResult.getResultCode() != ResultCode.SUCCESS) 758 { 759 err("An error occurred while attempting to search for " + 760 "conflicts with " + a.getName() + " value '" + value + 761 "' (as found in entry '" + searchEntry.getDN() + 762 "') below '" + baseDN + "': " + 763 searchResult.getDiagnosticMessage()); 764 conflictCounts.get(attrName).incrementAndGet(); 765 break baseDNLoop; 766 } 767 } 768 } 769 } 770 } 771 } 772 finally 773 { 774 final long count = entriesExamined.incrementAndGet(); 775 if ((count % 1000L) == 0L) 776 { 777 out(count, " entries examined"); 778 } 779 } 780 } 781 782 783 784 /** 785 * Indicates that the provided search result reference has been returned by 786 * the server and may be processed by this search result listener. 787 * 788 * @param searchReference The search result reference that has been returned 789 * by the server. 790 */ 791 public void searchReferenceReturned( 792 final SearchResultReference searchReference) 793 { 794 // No implementation is required. This tool will not follow referrals. 795 } 796}