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}