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 */
021 package com.unboundid.ldap.sdk.examples;
022
023
024
025 import java.io.OutputStream;
026 import java.util.Collections;
027 import java.util.LinkedHashMap;
028 import java.util.LinkedHashSet;
029 import java.util.List;
030 import java.util.Map;
031 import java.util.TreeMap;
032 import java.util.concurrent.atomic.AtomicLong;
033
034 import com.unboundid.asn1.ASN1OctetString;
035 import com.unboundid.ldap.sdk.Attribute;
036 import com.unboundid.ldap.sdk.DereferencePolicy;
037 import com.unboundid.ldap.sdk.DN;
038 import com.unboundid.ldap.sdk.Filter;
039 import com.unboundid.ldap.sdk.LDAPConnection;
040 import com.unboundid.ldap.sdk.LDAPException;
041 import com.unboundid.ldap.sdk.LDAPSearchException;
042 import com.unboundid.ldap.sdk.ResultCode;
043 import com.unboundid.ldap.sdk.SearchRequest;
044 import com.unboundid.ldap.sdk.SearchResult;
045 import com.unboundid.ldap.sdk.SearchResultEntry;
046 import com.unboundid.ldap.sdk.SearchResultReference;
047 import com.unboundid.ldap.sdk.SearchResultListener;
048 import com.unboundid.ldap.sdk.SearchScope;
049 import com.unboundid.ldap.sdk.Version;
050 import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl;
051 import com.unboundid.util.Debug;
052 import com.unboundid.util.LDAPCommandLineTool;
053 import com.unboundid.util.StaticUtils;
054 import com.unboundid.util.ThreadSafety;
055 import com.unboundid.util.ThreadSafetyLevel;
056 import com.unboundid.util.args.ArgumentException;
057 import com.unboundid.util.args.ArgumentParser;
058 import com.unboundid.util.args.DNArgument;
059 import com.unboundid.util.args.FilterArgument;
060 import com.unboundid.util.args.IntegerArgument;
061 import 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)
097 public 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
720 baseDNLoop:
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 }